* [PULL 00/58] Misc patches for 2026-04-30
@ 2026-04-30 17:21 Paolo Bonzini
2026-04-30 17:21 ` [PULL 02/58] target/arm/hvf, target/i386/hvf: Pass MR-relative offset to memory_region_set_dirty() Paolo Bonzini
` (57 more replies)
0 siblings, 58 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel
The following changes since commit aa15257174da180c6a8a9d58f87319cfe61c5520:
Merge tag 'pbouvier/pr/plugins-20260424' of https://gitlab.com/p-b-o/qemu into staging (2026-04-25 10:22:04 -0400)
are available in the Git repository at:
https://gitlab.com/bonzini/qemu.git tags/for-upstream
for you to fetch changes up to f0eb0ef4d83068fc3d909a0266832c7c5fbccb7a:
whpx: i386: documentation update (2026-04-30 17:55:04 +0200)
----------------------------------------------------------------
* hw/qdev: Consolidate qdev_get_printable_name() into qdev_get_human_name()
* target/i386: add new models for GMET, MMIO/GDS/RFDS mitigation and MBEC
* whpx improvements
* bump meson to 1.11.1
* tests: add test for json-streamer.c error recovery
* kconfig cleanups
* target/arm/hvf, target/i386/hvf: Pass MR-relative offset to memory_region_set_dirty()
----------------------------------------------------------------
Alessandro Ratti (3):
hw/qdev: Clarify fallback order in qdev_get_printable_name()
hw/qdev: Prefix bus type in qdev_get_printable_name() device paths
hw/qdev: Consolidate qdev_get_printable_name() into qdev_get_human_name()
Jon Kohler (3):
target/i386: add new AMD EPYC models for GMET enablement
target/i386: add new Intel models for MMIO/GDS/RFDS mitigation status
target/i386: add new Intel models for MBEC enablement
Magnus Kulke (1):
target/i386: add de/compaction to xsave_helper
Mohamed Mediouni (34):
whpx: i386: x2apic emulation
whpx: i386: wire up feature probing
whpx: i386: disable TbFlushHypercalls for emulated LAPIC
whpx: i386: enable x2apic by default for user-mode LAPIC
whpx: i386: reintroduce enlightenments for Windows 10
whpx: i386: introduce proper cpuid support
whpx: i386: kernel-irqchip=off fixes
whpx: i386: use WHvX64RegisterCr8 only when kernel-irqchip=off
whpx: i386: disable kernel-irqchip on Windows 10 when PIC enabled
whpx: i386: IO port fast path cleanup
whpx: i386: disable enlightenments and LAPIC for isapc
whpx: i386: interrupt priority support
hw/intc: apic: disallow APIC reads when disabled
whpx: i386: fix CPUID[1:EDX].APIC reporting
whpx: i386: set apicbase value only on success
whpx: i386: enable GuestIdleReg enlightenment
whpx: i386: unknown MSR configurability
whpx: i386: don't increment eip on MSR access raising GPF
target/i386: emulate, hvf: rdmsr/wrmsr GPF handling
whpx: i386: tighten APIC base validity check
whpx: i386: ignore vpassist when kernel-irqchip=off
target: i386: HLT type that ignores EFLAGS.IF
whpx: i386: add HV_X64_MSR_GUEST_IDLE when !kernel-irqchip
whpx: i386: some x2APIC awareness
whpx: i386: set WHvX64RegisterInitialApicId
whpx: i386: Pause VM on fatal exception to be able to inspect state
target/i386: emulate: use exception_payload for fault address
target/i386: make xsave_buf present unconditionally
whpx: xsave support
whpx: i386: set APIC ID only when APIC present
whpx: i386: update migration blocker message
whpx: i386: add feature to intercept #GP MSR accesses
whpx: i386: add SeparateSecurityDomain flag and make default
whpx: i386: documentation update
Paolo Bonzini (15):
target/i386/tcg: simplify decoding of 0F 38 F0...FF
tests: add test for json-streamer.c error recovery
kconfig: remove duplicate declaration of CONFIG_CXL
kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA
minikconf: run through isort
minikconf: small cleanups and dead code removal
minikconf: move command-line assignment out of the parser
minikconf: fix type mismatch in do_declaration
minikconf: simplify self.tok
minikconf: modernize handling of include chain
minikconf: use .items()
minikconf: pull main program into a function
minikconf: remove unnecessary semicolons
minikconf: replace else with early return and avoid unnecessary else
minikconf: add mypy annotations
Pierrick Bouvier (1):
pythondeps: bump to meson 1.11.1
Scott J. Goldman (1):
target/arm/hvf, target/i386/hvf: Pass MR-relative offset to memory_region_set_dirty()
docs/system/whpx.rst | 40 +-
include/hw/core/qdev.h | 28 +-
include/system/whpx-all.h | 1 +
include/system/whpx-common.h | 2 +-
include/system/whpx-internal.h | 30 +
target/i386/cpu.h | 14 +-
target/i386/emulate/x86_emu.h | 4 +-
target/i386/hvf/hvf-i386.h | 4 +-
target/i386/whpx/whpx-i386.h | 12 +
accel/whpx/whpx-common.c | 7 +
hw/core/qdev.c | 38 +-
hw/intc/apic.c | 9 +
hw/virtio/virtio.c | 6 +-
target/arm/hvf/hvf.c | 2 +-
target/arm/whpx/whpx-all.c | 5 +
target/i386/cpu.c | 169 +++-
target/i386/emulate/x86_emu.c | 10 +-
target/i386/emulate/x86_mmu.c | 3 +-
target/i386/hvf/hvf.c | 11 +-
target/i386/hvf/x86hvf.c | 4 +-
target/i386/whpx/whpx-all.c | 1396 ++++++++++++++++++++++-----
target/i386/whpx/whpx-apic.c | 83 +-
target/i386/whpx/whpx-cpu-legacy.c | 158 +++
target/i386/xsave_helper.c | 256 +++++
tests/unit/check-json-parser.c | 159 +++
target/i386/tcg/decode-new.c.inc | 21 +-
hw/Kconfig | 1 -
hw/cxl/Kconfig | 3 -
hw/misc/Kconfig | 8 -
hw/riscv/Kconfig | 4 +
python/scripts/vendor.py | 4 +-
python/tests/linters.py | 6 +
python/wheels/meson-1.10.0-py3-none-any.whl | Bin 1057029 -> 0 bytes
python/wheels/meson-1.11.1-py3-none-any.whl | Bin 0 -> 1078534 bytes
pythondeps.toml | 4 +-
scripts/minikconf.py | 465 ++++-----
target/i386/meson.build | 7 +-
target/i386/whpx/meson.build | 1 +
tests/unit/meson.build | 1 +
39 files changed, 2384 insertions(+), 592 deletions(-)
create mode 100644 target/i386/whpx/whpx-i386.h
create mode 100644 target/i386/whpx/whpx-cpu-legacy.c
create mode 100644 tests/unit/check-json-parser.c
delete mode 100644 hw/cxl/Kconfig
delete mode 100644 python/wheels/meson-1.10.0-py3-none-any.whl
create mode 100644 python/wheels/meson-1.11.1-py3-none-any.whl
--
2.54.0
^ permalink raw reply [flat|nested] 63+ messages in thread
* [PULL 02/58] target/arm/hvf, target/i386/hvf: Pass MR-relative offset to memory_region_set_dirty()
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 03/58] target/i386/tcg: simplify decoding of 0F 38 F0...FF Paolo Bonzini
` (56 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Scott J. Goldman
From: "Scott J. Goldman" <scottjgo@gmail.com>
Both the arm and i386 hvf accelerators have the same bug in their
dirty-page logging path: the address fed to memory_region_set_dirty()
is computed as "<ipa,gpa>_page + xlat", but memory_region_set_dirty()
expects an offset relative to the start of the resolved MemoryRegion.
address_space_translate() already returns that offset in xlat, while
ipa_page / gpa_page is the guest-physical (system address space)
address.
Adding the two together produces a bogus offset that for any non-
trivial RAM size walks well past the end of the MemoryRegion's dirty
bitmap. With dirty logging active (e.g. live migration on a guest
with several GB of RAM), this triggers an out-of-bounds atomic write
inside bitmap_set_atomic() and crashes the source QEMU as soon as the
guest writes to RAM:
Thread .. 'CPU N/HVF', stop reason = EXC_BAD_ACCESS ...
bitmap_set_atomic at bitmap.c:213
physical_memory_set_dirty_range at physmem.c:1038
memory_region_set_dirty at memory.c:2191
hvf_handle_exception at hvf.c
Fix it by passing only the MR-relative offset xlat. ipa_page /
gpa_page is still the right argument to hvf_unprotect_dirty_range(),
which works on the guest-physical address space.
Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
Link: https://lore.kernel.org/r/20260427232116.50586-2-scottjgo@gmail.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/arm/hvf/hvf.c | 2 +-
target/i386/hvf/hvf.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 678afe5c8e1..2146c5c87c6 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -2159,7 +2159,7 @@ static int hvf_handle_exception(CPUState *cpu, hv_vcpu_exit_exception_t *excp)
assert(!mr->readonly);
if (memory_region_get_dirty_log_mask(mr)) {
- memory_region_set_dirty(mr, ipa_page + xlat, page_size);
+ memory_region_set_dirty(mr, xlat, page_size);
hvf_unprotect_dirty_range(ipa_page, page_size);
}
diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c
index c0d028b1473..cdc8bd19504 100644
--- a/target/i386/hvf/hvf.c
+++ b/target/i386/hvf/hvf.c
@@ -146,7 +146,7 @@ static bool ept_emulation_fault(CPUState *cs, uint64_t gpa, uint64_t ept_qual)
if (write && memory_region_get_dirty_log_mask(mr)) {
uintptr_t page_size = qemu_real_host_page_size();
- memory_region_set_dirty(mr, gpa_page + xlat, page_size);
+ memory_region_set_dirty(mr, xlat, page_size);
hvf_unprotect_dirty_range(gpa_page, page_size);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 03/58] target/i386/tcg: simplify decoding of 0F 38 F0...FF
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
2026-04-30 17:21 ` [PULL 02/58] target/arm/hvf, target/i386/hvf: Pass MR-relative offset to memory_region_set_dirty() Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 04/58] tests: add test for json-streamer.c error recovery Paolo Bonzini
` (55 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel
These lines are shown in the manual with a weird representation that
confers a special meaning to 0x66 0xF2 prefixes. In reality, this is
just the CRC32 instruction (chosen by 0xF2) plus a data size override
prefix. All other instruction in the range that use the 0xF2 prefix
are VEX-encoded and therefore they do not support multiple prefixes.
Because of this, it is possible to handle the four prefixes normally
using decode_by_prefix; the 0x66 0xF2 combination for CRC32 is handled
naturally by the "v" operand size.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/tcg/decode-new.c.inc | 21 +++------------------
1 file changed, 3 insertions(+), 18 deletions(-)
diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc
index c8b5bd6ad26..ac181308ca4 100644
--- a/target/i386/tcg/decode-new.c.inc
+++ b/target/i386/tcg/decode-new.c.inc
@@ -873,8 +873,8 @@ static const X86OpEntry opcodes_0F38_00toEF[240] = {
[0xef] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66),
};
-/* five rows for no prefix, 66, F3, F2, 66+F2 */
-static const X86OpEntry opcodes_0F38_F0toFF[16][5] = {
+/* four rows for no prefix, 66, F3, F2 (including 66+F2 operand size override) */
+static const X86OpEntry opcodes_0F38_F0toFF[16][4] = {
/*
* MOVBE and CRC32 are incorrectly listed as always doing 32-bit operation
* without prefix and 16-bit operation with 0x66.
@@ -884,49 +884,42 @@ static const X86OpEntry opcodes_0F38_F0toFF[16][5] = {
X86_OP_ENTRYwr(MOVBE, G,v, M,v, cpuid(MOVBE)),
{},
X86_OP_ENTRY2(CRC32, G,d, E,b, cpuid(SSE42)),
- X86_OP_ENTRY2(CRC32, G,d, E,b, cpuid(SSE42)),
},
[1] = {
X86_OP_ENTRYwr(MOVBE, M,v, G,v, cpuid(MOVBE)),
X86_OP_ENTRYwr(MOVBE, M,v, G,v, cpuid(MOVBE)),
{},
X86_OP_ENTRY2(CRC32, G,d, E,v, cpuid(SSE42)),
- X86_OP_ENTRY2(CRC32, G,d, E,v, cpuid(SSE42)),
},
[2] = {
X86_OP_ENTRY3(ANDN, G,y, B,y, E,y, vex13 cpuid(BMI1)),
{},
{},
{},
- {},
},
[3] = {
X86_OP_GROUP3(group17, B,y, None,None, E,y, vex13 cpuid(BMI1)),
{},
{},
{},
- {},
},
[5] = {
X86_OP_ENTRY3(BZHI, G,y, E,y, B,y, vex13 cpuid(BMI1)),
{},
X86_OP_ENTRY3(PEXT, G,y, B,y, E,y, vex13 zextT0 cpuid(BMI2)),
X86_OP_ENTRY3(PDEP, G,y, B,y, E,y, vex13 zextT0 cpuid(BMI2)),
- {},
},
[6] = {
{},
X86_OP_ENTRY2(ADCX, G,y, E,y, cpuid(ADX)),
X86_OP_ENTRY2(ADOX, G,y, E,y, cpuid(ADX)),
X86_OP_ENTRY3(MULX, /* B,y, */ G,y, E,y, 2,y, vex13 cpuid(BMI2)),
- {},
},
[7] = {
X86_OP_ENTRY3(BEXTR, G,y, E,y, B,y, vex13 zextT0 cpuid(BMI1)),
X86_OP_ENTRY3(SHLX, G,y, E,y, B,y, vex13 cpuid(BMI1)),
X86_OP_ENTRY3(SARX, G,y, E,y, B,y, vex13 sextT0 cpuid(BMI1)),
X86_OP_ENTRY3(SHRX, G,y, E,y, B,y, vex13 zextT0 cpuid(BMI1)),
- {},
},
};
@@ -936,15 +929,7 @@ static void decode_0F38(DisasContext *s, CPUX86State *env, X86OpEntry *entry, ui
if (*b < 0xf0) {
*entry = opcodes_0F38_00toEF[*b];
} else {
- int row = 0;
- if (s->prefix & PREFIX_REPZ) {
- /* The REPZ (F3) prefix has priority over 66 */
- row = 2;
- } else {
- row += s->prefix & PREFIX_REPNZ ? 3 : 0;
- row += s->prefix & PREFIX_DATA ? 1 : 0;
- }
- *entry = opcodes_0F38_F0toFF[*b & 15][row];
+ *entry = *decode_by_prefix(s, opcodes_0F38_F0toFF[*b & 15]);
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 04/58] tests: add test for json-streamer.c error recovery
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
2026-04-30 17:21 ` [PULL 02/58] target/arm/hvf, target/i386/hvf: Pass MR-relative offset to memory_region_set_dirty() Paolo Bonzini
2026-04-30 17:21 ` [PULL 03/58] target/i386/tcg: simplify decoding of 0F 38 F0...FF Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 05/58] kconfig: remove duplicate declaration of CONFIG_CXL Paolo Bonzini
` (54 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Markus Armbruster
Before rewriting the error recovery code to work in a push parsing
setup, make sure that we have tests for it.
Cover various cases of invalid JSON, to check that structural
recovery based on balanced brackets and braces works; and
lexer-based recovery which documents "\f" as a sure fire
way to reset the lexer.
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
tests/unit/check-json-parser.c | 159 +++++++++++++++++++++++++++++++++
tests/unit/meson.build | 1 +
2 files changed, 160 insertions(+)
create mode 100644 tests/unit/check-json-parser.c
diff --git a/tests/unit/check-json-parser.c b/tests/unit/check-json-parser.c
new file mode 100644
index 00000000000..656a4752470
--- /dev/null
+++ b/tests/unit/check-json-parser.c
@@ -0,0 +1,159 @@
+/*
+ * Unit tests for JSON Parser error recovery
+ *
+ * Copyright 2026 Red Hat
+ * Author: Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ */
+
+/*
+ * Missing tests:
+ * - multiple JSON values in a single stream
+ * - multiple invocations of json_message_parser_feed()
+ * (does not really matter much because of how
+ * json_lexer_feed() is implemented)
+ * - most JSON types are only covered by check-json.c.
+ */
+
+#include "qemu/osdep.h"
+
+#include "qapi/error.h"
+#include "qobject/qbool.h"
+#include "qobject/json-parser.h"
+
+typedef struct ParseResult {
+ int errors;
+ QObject *result;
+} ParseResult;
+
+static void parse_emit(void *opaque, QObject *json, Error *err)
+{
+ ParseResult *r = opaque;
+
+ g_assert_cmpint(!json, !=, !err);
+ if (err) {
+ r->errors++;
+ error_free(err);
+ } else {
+ g_assert_null(r->result);
+ r->result = json;
+ }
+}
+
+static ParseResult do_parse(const char *input)
+{
+ ParseResult r = { 0, NULL };
+ JSONMessageParser parser;
+
+ json_message_parser_init(&parser, parse_emit, &r, NULL);
+ json_message_parser_feed(&parser, input, strlen(input));
+ json_message_parser_flush(&parser);
+ json_message_parser_destroy(&parser);
+ return r;
+}
+
+static void check_result(const char *input, int expected_errors,
+ QType expected_type)
+{
+ ParseResult r = do_parse(input);
+
+ g_assert_cmpint(r.errors, ==, expected_errors);
+ g_assert_nonnull(r.result);
+ g_assert_cmpint(qobject_type(r.result), ==, expected_type);
+ qobject_unref(r.result);
+}
+
+static void check_result_error(const char *input, int expected_errors)
+{
+ ParseResult r = do_parse(input);
+
+ g_assert_cmpint(r.errors, ==, expected_errors);
+ g_assert_null(r.result);
+}
+
+static void test_simple(void)
+{
+ check_result("false", 0, QTYPE_QBOOL);
+}
+
+static void test_whitespace(void)
+{
+ check_result(" false", 0, QTYPE_QBOOL);
+}
+
+static void test_extra_closing_braces(void)
+{
+ check_result("}}false", 2, QTYPE_QBOOL);
+}
+
+static void test_bad_dict(void)
+{
+ check_result("{ 'abc' }false", 1, QTYPE_QBOOL);
+}
+
+static void test_trailing_comma(void)
+{
+ check_result("[ 'abc', ]false", 1, QTYPE_QBOOL);
+}
+
+static void test_lexer_recovery(void)
+{
+ check_result("\f{}", 1, QTYPE_QDICT);
+ check_result("\f[]", 1, QTYPE_QLIST);
+ check_result("\f:false", 2, QTYPE_QBOOL);
+ check_result("\f,false", 2, QTYPE_QBOOL);
+
+ /*
+ * Alphabetic characters do not start a new parsing. This is
+ * slightly weird but it keeps the lexer simple and works well for
+ * QMP (where valid input is a sequence of dictionaries).
+ */
+ check_result_error("\ffalse", 1);
+ check_result_error("\f'str'", 1);
+ check_result_error("\f\"str\"", 1);
+}
+
+static void test_lexer_recovery_nested(void)
+{
+ check_result("{[{\f{}", 1, QTYPE_QDICT);
+ check_result("{[{\f[]", 1, QTYPE_QLIST);
+ check_result("{[{\f:false", 2, QTYPE_QBOOL);
+ check_result("{[{\f,false", 2, QTYPE_QBOOL);
+
+ /*
+ * As in test_lexer_recovery, these do not produce a successful
+ * parse after \f.
+ */
+ check_result_error("{[{\ffalse", 1);
+ check_result_error("{[{\f'str'", 1);
+ check_result_error("{[{\f\"str\"", 1);
+}
+
+static void test_nested(void)
+{
+ check_result("[{'a']}false", 1, QTYPE_QBOOL);
+}
+
+static void test_nested_multiple(void)
+{
+ check_result("[{'a']}[{'a']}false", 2, QTYPE_QBOOL);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/json-parser/simple", test_simple);
+ g_test_add_func("/json-parser/whitespace", test_whitespace);
+ g_test_add_func("/json-parser/error-recovery/extra-closing-braces", test_extra_closing_braces);
+ g_test_add_func("/json-parser/error-recovery/bad-dict", test_bad_dict);
+ g_test_add_func("/json-parser/error-recovery/trailing-comma", test_trailing_comma);
+ g_test_add_func("/json-parser/error-recovery/lexer", test_lexer_recovery);
+ g_test_add_func("/json-parser/error-recovery/lexer/nested", test_lexer_recovery_nested);
+ g_test_add_func("/json-parser/error-recovery/nested", test_nested);
+ g_test_add_func("/json-parser/error-recovery/nested/multiple", test_nested_multiple);
+
+ return g_test_run();
+}
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index 41e8b06c339..03d36748c73 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -10,6 +10,7 @@ tests = {
'check-qnull': [],
'check-qobject': [],
'check-qjson': [],
+ 'check-json-parser': [],
'check-qlit': [],
'test-error-report': [],
'test-qobject-output-visitor': [testqapi],
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 05/58] kconfig: remove duplicate declaration of CONFIG_CXL
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (2 preceding siblings ...)
2026-04-30 17:21 ` [PULL 04/58] tests: add test for json-streamer.c error recovery Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA Paolo Bonzini
` (53 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
hw/Kconfig | 1 -
hw/cxl/Kconfig | 3 ---
2 files changed, 4 deletions(-)
delete mode 100644 hw/cxl/Kconfig
diff --git a/hw/Kconfig b/hw/Kconfig
index b3ed092f7a8..c109f5537b2 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -6,7 +6,6 @@ source audio/Kconfig
source block/Kconfig
source char/Kconfig
source core/Kconfig
-source cxl/Kconfig
source display/Kconfig
source dma/Kconfig
source fsi/Kconfig
diff --git a/hw/cxl/Kconfig b/hw/cxl/Kconfig
deleted file mode 100644
index 8e67519b161..00000000000
--- a/hw/cxl/Kconfig
+++ /dev/null
@@ -1,3 +0,0 @@
-config CXL
- bool
- default y if PCI_EXPRESS
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (3 preceding siblings ...)
2026-04-30 17:21 ` [PULL 05/58] kconfig: remove duplicate declaration of CONFIG_CXL Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-05-04 6:28 ` Philippe Mathieu-Daudé
2026-04-30 17:21 ` [PULL 07/58] minikconf: run through isort Paolo Bonzini
` (52 subsequent siblings)
57 siblings, 1 reply; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
hw/misc/Kconfig | 8 --------
hw/riscv/Kconfig | 4 ++++
2 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index f4d49248c08..99bdf09219a 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -135,14 +135,6 @@ config RISCV_MIPS_CPC
config RISCV_MIPS_CPS
bool
-config MIPS_BOSTON_AIA
- bool
- default y
- depends on RISCV64
- select RISCV_MIPS_CMGCR
- select RISCV_MIPS_CPC
- select RISCV_MIPS_CPS
-
config MPS2_FPGAIO
bool
select LED
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 0222c93f878..2518b04175f 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -132,5 +132,9 @@ config XIANGSHAN_KUNMINGHU
config MIPS_BOSTON_AIA
bool
default y
+ depends on RISCV64
select PCI_EXPRESS
select PCI_EXPRESS_XILINX
+ select RISCV_MIPS_CMGCR
+ select RISCV_MIPS_CPC
+ select RISCV_MIPS_CPS
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 07/58] minikconf: run through isort
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (4 preceding siblings ...)
2026-04-30 17:21 ` [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 08/58] minikconf: small cleanups and dead code removal Paolo Bonzini
` (51 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
python/tests/linters.py | 3 +++
scripts/minikconf.py | 4 ++--
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/python/tests/linters.py b/python/tests/linters.py
index 9696c0b71b8..4ce4e8a9805 100644
--- a/python/tests/linters.py
+++ b/python/tests/linters.py
@@ -25,6 +25,9 @@ def test_flake8_qapi(self):
]
)
+ def test_isort_minikconf(self):
+ check_call([sys.executable, "-m", "isort", "-c", "../scripts/minikconf.py"])
+
def test_isort_pkg(self):
check_call([sys.executable, "-m", "isort", "-c", "qemu/"])
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 4de5aeed11a..24389763db0 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -12,9 +12,9 @@
# the top-level directory.
import os
-import sys
-import re
import random
+import re
+import sys
__all__ = [ 'KconfigDataError', 'KconfigParserError',
'KconfigData', 'KconfigParser' ,
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 08/58] minikconf: small cleanups and dead code removal
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (5 preceding siblings ...)
2026-04-30 17:21 ` [PULL 07/58] minikconf: run through isort Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 09/58] minikconf: move command-line assignment out of the parser Paolo Bonzini
` (50 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 24389763db0..08df8d5dabb 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -108,7 +108,7 @@ def __str__(self):
return self.name
def has_value(self):
- return not (self.value is None)
+ return self.value is not None
def set_value(self, val, clause):
self.clauses_for_var.append(clause)
if self.has_value() and self.value != val:
@@ -158,7 +158,7 @@ def __init__(self, dest, value, cond=None):
KconfigData.Clause.__init__(self, dest)
self.value = value
self.cond = cond
- if not (self.cond is None):
+ if self.cond is not None:
self.cond.add_edges_to(self.dest)
def __str__(self):
value = 'y' if self.value else 'n'
@@ -212,7 +212,7 @@ def __init__(self, value_mangler=defconfig):
def check_undefined(self):
undef = False
for i in self.referenced_vars:
- if not (i in self.defined_vars):
+ if i not in self.defined_vars:
print("undefined symbol %s" % (i), file=sys.stderr)
undef = True
return undef
@@ -220,7 +220,6 @@ def check_undefined(self):
def compute_config(self):
if self.check_undefined():
raise KconfigDataError("there were undefined symbols")
- return None
debug_print("Input:")
for clause in self.clauses:
@@ -270,7 +269,7 @@ def do_declaration(self, var):
# var is a string with the variable's name.
def do_var(self, var):
- if (var in self.referenced_vars):
+ if var in self.referenced_vars:
return self.referenced_vars[var]
var_obj = self.referenced_vars[var] = KconfigData.Var(var)
@@ -339,9 +338,9 @@ def __str__(self):
class KconfigParser:
@classmethod
- def parse(self, fp, mode=None):
+ def parse(cls, fp, mode=None):
data = KconfigData(mode or defconfig)
- parser = KconfigParser(data)
+ parser = cls(data)
parser.parse_file(fp)
return data
@@ -352,9 +351,10 @@ def parse_file(self, fp):
self.abs_fname = os.path.abspath(fp.name)
self.fname = fp.name
self.data.previously_included.append(self.abs_fname)
- self.src = fp.read()
- if self.src == '' or self.src[-1] != '\n':
- self.src += '\n'
+ src = fp.read()
+ if src == '' or src[-1] != '\n':
+ src += '\n'
+ self.src = src
self.cursor = 0
self.line = 1
self.line_pos = 0
@@ -534,7 +534,6 @@ def parse_property(self, var):
# properties: properties property
# | /* empty */
def parse_properties(self, var):
- had_default = False
while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \
self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
self.tok == TOK_IMPLY:
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 09/58] minikconf: move command-line assignment out of the parser
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (6 preceding siblings ...)
2026-04-30 17:21 ` [PULL 08/58] minikconf: small cleanups and dead code removal Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 10/58] minikconf: fix type mismatch in do_declaration Paolo Bonzini
` (49 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
KconfigParser.do_assignment() only exists to handle CONFIG_FOO=y/n
arguments from the command line; it is never invoked while parsing
a Kconfig source file. Because main() called it on a parser that
had never been through parse_file(), a failing CONFIG_ check would
raise a KconfigParserError whose __init__ and location() touch
fields of "self" that do not exist yet. The regex in main()
currently shields this, but it is fragile.
Move the prefix-stripping assignment to KconfigData as
do_cmdline_assignment(), simplify KconfigParser.parse() to accept
an existing KconfigData, and call it from main() so the parser is
only used for actual file parsing.
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 27 +++++++++------------------
1 file changed, 9 insertions(+), 18 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 08df8d5dabb..668de577d09 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -278,6 +278,10 @@ def do_var(self, var):
def do_assignment(self, var, val):
self.clauses.append(KconfigData.AssignmentClause(var, val))
+ def do_cmdline_assignment(self, var, val):
+ assert var.startswith("CONFIG_")
+ self.do_assignment(self.do_var(var[7:]), val)
+
def do_default(self, var, val, cond=None):
val = self.value_mangler(val)
self.clauses.append(KconfigData.DefaultClause(var, val, cond))
@@ -338,11 +342,8 @@ def __str__(self):
class KconfigParser:
@classmethod
- def parse(cls, fp, mode=None):
- data = KconfigData(mode or defconfig)
- parser = cls(data)
- parser.parse_file(fp)
- return data
+ def parse(cls, fp, data):
+ cls(data).parse_file(fp)
def __init__(self, data):
self.data = data
@@ -361,14 +362,6 @@ def parse_file(self, fp):
self.get_token()
self.parse_config()
- def do_assignment(self, var, val):
- if not var.startswith("CONFIG_"):
- raise KconfigParserError(
- self, "assigned variable should start with CONFIG_"
- )
- var = self.data.do_var(var[7:])
- self.data.do_assignment(var, val)
-
# file management -----
def error_path(self):
@@ -688,18 +681,16 @@ def scan_token(self):
sys.exit(1)
data = KconfigData(mode)
- parser = KconfigParser(data)
external_vars = set()
for arg in argv[3:]:
m = re.match(r'^(CONFIG_[A-Z0-9_]+)=([yn]?)$', arg)
if m is not None:
name, value = m.groups()
- parser.do_assignment(name, value == 'y')
+ data.do_cmdline_assignment(name, value == 'y')
external_vars.add(name[7:])
else:
- fp = open(arg, 'rt', encoding='utf-8')
- parser.parse_file(fp)
- fp.close()
+ with open(arg, 'rt', encoding='utf-8') as fp:
+ KconfigParser.parse(fp, data)
config = data.compute_config()
for key in sorted(config.keys()):
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 10/58] minikconf: fix type mismatch in do_declaration
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (7 preceding siblings ...)
2026-04-30 17:21 ` [PULL 09/58] minikconf: move command-line assignment out of the parser Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 11/58] minikconf: simplify self.tok Paolo Bonzini
` (48 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
The set stores strings, not Vars. Because of this the duplicate
definition check did not work.
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 668de577d09..2656d364920 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -262,8 +262,8 @@ def visit_fn(var):
# semantic actions -------------
def do_declaration(self, var):
- if (var in self.defined_vars):
- raise KconfigDataError('variable "' + var + '" defined twice')
+ if var.name in self.defined_vars:
+ raise KconfigDataError('variable "%s" defined twice' % var.name)
self.defined_vars.add(var.name)
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 11/58] minikconf: simplify self.tok
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (8 preceding siblings ...)
2026-04-30 17:21 ` [PULL 10/58] minikconf: fix type mismatch in do_declaration Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 12/58] minikconf: modernize handling of include chain Paolo Bonzini
` (47 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Do not ever store a string in self.tok, only a finished token.
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 59 ++++++++++++++++++++++----------------------
1 file changed, 30 insertions(+), 29 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 2656d364920..3d63cc6b21f 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -330,9 +330,9 @@ def do_imply(self, var, symbol, cond=None):
class KconfigParserError(Exception):
def __init__(self, parser, msg, tok=None):
self.loc = parser.location()
- tok = tok or parser.tok
+ tok = tok if tok is not None else parser.tok
if tok != TOK_NONE:
- location = TOKENS.get(tok, None) or ('"%s"' % tok)
+ location = TOKENS[tok] if isinstance(tok, int) else '"%s"' % tok
msg = '%s before %s' % (msg, location)
self.msg = msg
@@ -573,13 +573,14 @@ def parse_config(self):
def get_token(self):
while True:
- self.tok = self.src[self.cursor]
+ ch = self.src[self.cursor]
self.pos = self.cursor
self.cursor += 1
self.val = None
- self.tok = self.scan_token()
- if self.tok is not None:
+ tok = self.scan_token(ch)
+ if tok is not None:
+ self.tok = tok
return
def check_keyword(self, rest):
@@ -591,46 +592,46 @@ def check_keyword(self, rest):
self.cursor += length
return True
- def scan_token(self):
- if self.tok == '#':
+ def scan_token(self, ch):
+ if ch == '#':
self.cursor = self.src.find('\n', self.cursor)
return None
- elif self.tok == '=':
+ elif ch == '=':
return TOK_EQUAL
- elif self.tok == '(':
+ elif ch == '(':
return TOK_LPAREN
- elif self.tok == ')':
+ elif ch == ')':
return TOK_RPAREN
- elif self.tok == '&' and self.src[self.pos+1] == '&':
+ elif ch == '&' and self.src[self.pos+1] == '&':
self.cursor += 1
return TOK_AND
- elif self.tok == '|' and self.src[self.pos+1] == '|':
+ elif ch == '|' and self.src[self.pos+1] == '|':
self.cursor += 1
return TOK_OR
- elif self.tok == '!':
+ elif ch == '!':
return TOK_NOT
- elif self.tok == 'd' and self.check_keyword("epends"):
+ elif ch == 'd' and self.check_keyword("epends"):
return TOK_DEPENDS
- elif self.tok == 'o' and self.check_keyword("n"):
+ elif ch == 'o' and self.check_keyword("n"):
return TOK_ON
- elif self.tok == 's' and self.check_keyword("elect"):
+ elif ch == 's' and self.check_keyword("elect"):
return TOK_SELECT
- elif self.tok == 'i' and self.check_keyword("mply"):
+ elif ch == 'i' and self.check_keyword("mply"):
return TOK_IMPLY
- elif self.tok == 'c' and self.check_keyword("onfig"):
+ elif ch == 'c' and self.check_keyword("onfig"):
return TOK_CONFIG
- elif self.tok == 'd' and self.check_keyword("efault"):
+ elif ch == 'd' and self.check_keyword("efault"):
return TOK_DEFAULT
- elif self.tok == 'b' and self.check_keyword("ool"):
+ elif ch == 'b' and self.check_keyword("ool"):
return TOK_BOOL
- elif self.tok == 'i' and self.check_keyword("f"):
+ elif ch == 'i' and self.check_keyword("f"):
return TOK_IF
- elif self.tok == 'y' and self.check_keyword(""):
+ elif ch == 'y' and self.check_keyword(""):
return TOK_Y
- elif self.tok == 'n' and self.check_keyword(""):
+ elif ch == 'n' and self.check_keyword(""):
return TOK_N
- elif (self.tok == 's' and self.check_keyword("ource")) or \
- self.tok == 'i' and self.check_keyword("nclude"):
+ elif (ch == 's' and self.check_keyword("ource")) or \
+ ch == 'i' and self.check_keyword("nclude"):
# source FILENAME
# include FILENAME
while self.src[self.cursor].isspace():
@@ -639,19 +640,19 @@ def scan_token(self):
self.cursor = self.src.find('\n', self.cursor)
self.val = self.src[start:self.cursor]
return TOK_SOURCE
- elif self.tok.isalnum():
+ elif ch.isalnum():
# identifier
while self.src[self.cursor].isalnum() or self.src[self.cursor] == '_':
self.cursor += 1
self.val = self.src[self.pos:self.cursor]
return TOK_ID
- elif self.tok == '\n':
+ elif ch == '\n':
if self.cursor == len(self.src):
return TOK_EOF
self.line += 1
self.line_pos = self.cursor
- elif not self.tok.isspace():
- raise KconfigParserError(self, 'invalid input')
+ elif not ch.isspace():
+ raise KconfigParserError(self, 'invalid input', ch)
return None
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 12/58] minikconf: modernize handling of include chain
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (9 preceding siblings ...)
2026-04-30 17:21 ` [PULL 11/58] minikconf: simplify self.tok Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 13/58] minikconf: use .items() Paolo Bonzini
` (46 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Use a dataclass, and store it in the parser to avoid having to
save and restore it.
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 68 +++++++++++++++++++++++++-------------------
1 file changed, 39 insertions(+), 29 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 3d63cc6b21f..c46c73f82d9 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -11,15 +11,36 @@
# or, at your option, any later version. See the COPYING file in
# the top-level directory.
+from __future__ import annotations
+
import os
import random
import re
import sys
+from dataclasses import dataclass
__all__ = [ 'KconfigDataError', 'KconfigParserError',
'KconfigData', 'KconfigParser' ,
'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ]
+@dataclass
+class IncludeInfo:
+ file: str
+ line: int
+ parent: IncludeInfo | None
+
+ def __iter__(self):
+ inf = self
+ while inf is not None:
+ yield "%s:%d" % (inf.file, inf.line)
+ inf = inf.parent
+
+ def error_path(self):
+ res = ""
+ for loc in self:
+ res = "In file included from %s:\n" % loc + res
+ return res
+
def debug_print(*args):
#print('# ' + (' '.join(str(x) for x in args)))
pass
@@ -202,7 +223,6 @@ def process(self):
def __init__(self, value_mangler=defconfig):
self.value_mangler = value_mangler
self.previously_included = []
- self.incl_info = None
self.defined_vars = set()
self.referenced_vars = dict()
self.clauses = list()
@@ -342,13 +362,12 @@ def __str__(self):
class KconfigParser:
@classmethod
- def parse(cls, fp, data):
- cls(data).parse_file(fp)
+ def parse(cls, fp, data, incl_info=None):
+ cls(fp, data, incl_info).parse_config()
- def __init__(self, data):
+ def __init__(self, fp, data, incl_info):
self.data = data
-
- def parse_file(self, fp):
+ self.incl_info = incl_info
self.abs_fname = os.path.abspath(fp.name)
self.fname = fp.name
self.data.previously_included.append(self.abs_fname)
@@ -360,19 +379,9 @@ def parse_file(self, fp):
self.line = 1
self.line_pos = 0
self.get_token()
- self.parse_config()
# file management -----
- def error_path(self):
- inf = self.data.incl_info
- res = ""
- while inf:
- res = ("In file included from %s:%d:\n" % (inf['file'],
- inf['line'])) + res
- inf = inf['parent']
- return res
-
def location(self):
col = 1
for ch in self.src[self.line_pos:self.pos]:
@@ -380,33 +389,34 @@ def location(self):
col += 8 - ((col - 1) % 8)
else:
col += 1
- return '%s%s:%d:%d' %(self.error_path(), self.fname, self.line, col)
+ inf = self.incl_info
+ incl_chain = inf.error_path() if inf is not None else ""
+ return '%s%s:%d:%d' % (incl_chain, self.fname, self.line, col)
def do_include(self, include):
incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname),
include)
# catch inclusion cycle
- inf = self.data.incl_info
+ inf = self.incl_info
while inf:
- if incl_abs_fname == os.path.abspath(inf['file']):
+ if incl_abs_fname == os.path.abspath(inf.file):
raise KconfigParserError(self, "Inclusion loop for %s"
% include)
- inf = inf['parent']
+ inf = inf.parent
# skip multiple include of the same file
if incl_abs_fname in self.data.previously_included:
return
try:
- fp = open(incl_abs_fname, 'rt', encoding='utf-8')
- except IOError as e:
- raise KconfigParserError(self,
- '%s: %s' % (e.strerror, include))
+ try:
+ fp = open(incl_abs_fname, 'rt', encoding='utf-8')
+ except IOError as e:
+ raise KconfigParserError(self, '%s: %s' % (e.strerror, include))
- inf = self.data.incl_info
- self.data.incl_info = { 'file': self.fname, 'line': self.line,
- 'parent': inf }
- KconfigParser(self.data).parse_file(fp)
- self.data.incl_info = inf
+ inner = IncludeInfo(file=self.fname, line=self.line, parent=self.incl_info)
+ type(self).parse(fp, self.data, inner)
+ finally:
+ fp.close()
# recursive descent parser -----
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 13/58] minikconf: use .items()
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (10 preceding siblings ...)
2026-04-30 17:21 ` [PULL 12/58] minikconf: modernize handling of include chain Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 14/58] minikconf: pull main program into a function Paolo Bonzini
` (45 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index c46c73f82d9..7f0b64b1e08 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -246,8 +246,8 @@ def compute_config(self):
debug_print(clause)
debug_print("\nDependency graph:")
- for i in self.referenced_vars:
- debug_print(i, "->", [str(x) for x in self.referenced_vars[i].outgoing])
+ for source, edges in self.referenced_vars.items():
+ debug_print(source, "->", [str(x) for x in edges.outgoing])
# The reverse of the depth-first order is the topological sort
dfo = dict()
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 14/58] minikconf: pull main program into a function
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (11 preceding siblings ...)
2026-04-30 17:21 ` [PULL 13/58] minikconf: use .items() Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 15/58] minikconf: remove unnecessary semicolons Paolo Bonzini
` (44 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Avoid pulluting the global namespace.
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 7f0b64b1e08..d728fd867f3 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -666,7 +666,7 @@ def scan_token(self, ch):
return None
-if __name__ == '__main__':
+def main() -> None:
argv = sys.argv
mode = defconfig
if len(sys.argv) > 1:
@@ -712,3 +712,6 @@ def scan_token(self, ch):
for fname in data.previously_included:
print ('%s: %s' % (argv[1], fname), file=deps)
deps.close()
+
+if __name__ == '__main__':
+ main()
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 15/58] minikconf: remove unnecessary semicolons
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (12 preceding siblings ...)
2026-04-30 17:21 ` [PULL 14/58] minikconf: pull main program into a function Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 16/58] minikconf: replace else with early return and avoid unnecessary else Paolo Bonzini
` (43 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index d728fd867f3..685e559bb87 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -327,25 +327,25 @@ def do_imply(self, var, symbol, cond=None):
# tokens table
TOKENS = {}
TOK_NONE = -1
-TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("';
-TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"';
-TOK_EQUAL = 2; TOKENS[TOK_EQUAL] = '"="';
-TOK_AND = 3; TOKENS[TOK_AND] = '"&&"';
-TOK_OR = 4; TOKENS[TOK_OR] = '"||"';
-TOK_NOT = 5; TOKENS[TOK_NOT] = '"!"';
-TOK_DEPENDS = 6; TOKENS[TOK_DEPENDS] = '"depends"';
-TOK_ON = 7; TOKENS[TOK_ON] = '"on"';
-TOK_SELECT = 8; TOKENS[TOK_SELECT] = '"select"';
-TOK_IMPLY = 9; TOKENS[TOK_IMPLY] = '"imply"';
-TOK_CONFIG = 10; TOKENS[TOK_CONFIG] = '"config"';
-TOK_DEFAULT = 11; TOKENS[TOK_DEFAULT] = '"default"';
-TOK_Y = 12; TOKENS[TOK_Y] = '"y"';
-TOK_N = 13; TOKENS[TOK_N] = '"n"';
-TOK_SOURCE = 14; TOKENS[TOK_SOURCE] = '"source"';
-TOK_BOOL = 15; TOKENS[TOK_BOOL] = '"bool"';
-TOK_IF = 16; TOKENS[TOK_IF] = '"if"';
-TOK_ID = 17; TOKENS[TOK_ID] = 'identifier';
-TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file';
+TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("'
+TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"'
+TOK_EQUAL = 2; TOKENS[TOK_EQUAL] = '"="'
+TOK_AND = 3; TOKENS[TOK_AND] = '"&&"'
+TOK_OR = 4; TOKENS[TOK_OR] = '"||"'
+TOK_NOT = 5; TOKENS[TOK_NOT] = '"!"'
+TOK_DEPENDS = 6; TOKENS[TOK_DEPENDS] = '"depends"'
+TOK_ON = 7; TOKENS[TOK_ON] = '"on"'
+TOK_SELECT = 8; TOKENS[TOK_SELECT] = '"select"'
+TOK_IMPLY = 9; TOKENS[TOK_IMPLY] = '"imply"'
+TOK_CONFIG = 10; TOKENS[TOK_CONFIG] = '"config"'
+TOK_DEFAULT = 11; TOKENS[TOK_DEFAULT] = '"default"'
+TOK_Y = 12; TOKENS[TOK_Y] = '"y"'
+TOK_N = 13; TOKENS[TOK_N] = '"n"'
+TOK_SOURCE = 14; TOKENS[TOK_SOURCE] = '"source"'
+TOK_BOOL = 15; TOKENS[TOK_BOOL] = '"bool"'
+TOK_IF = 16; TOKENS[TOK_IF] = '"if"'
+TOK_ID = 17; TOKENS[TOK_ID] = 'identifier'
+TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file'
class KconfigParserError(Exception):
def __init__(self, parser, msg, tok=None):
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 16/58] minikconf: replace else with early return and avoid unnecessary else
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (13 preceding siblings ...)
2026-04-30 17:21 ` [PULL 15/58] minikconf: remove unnecessary semicolons Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 17/58] minikconf: add mypy annotations Paolo Bonzini
` (42 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
scripts/minikconf.py | 79 ++++++++++++++++++++++----------------------
1 file changed, 40 insertions(+), 39 deletions(-)
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 685e559bb87..7f042daecbf 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -432,24 +432,24 @@ def parse_y_or_n(self):
# var: ID
def parse_var(self):
- if self.tok == TOK_ID:
- val = self.val
- self.get_token()
- return self.data.do_var(val)
- else:
+ if self.tok != TOK_ID:
raise KconfigParserError(self, 'Expected identifier')
+ val = self.val
+ assert val is not None
+ self.get_token()
+ return self.data.do_var(val)
# assignment_var: ID (starting with "CONFIG_")
def parse_assignment_var(self):
- if self.tok == TOK_ID:
- val = self.val
- if not val.startswith("CONFIG_"):
- raise KconfigParserError(self,
- 'Expected identifier starting with "CONFIG_"', TOK_NONE)
- self.get_token()
- return self.data.do_var(val[7:])
- else:
+ if self.tok != TOK_ID:
raise KconfigParserError(self, 'Expected identifier')
+ val = self.val
+ assert val is not None
+ if not val.startswith("CONFIG_"):
+ raise KconfigParserError(self,
+ 'Expected identifier starting with "CONFIG_"', TOK_NONE)
+ self.get_token()
+ return self.data.do_var(val[7:])
# assignment: var EQUAL y_or_n
def parse_assignment(self):
@@ -497,11 +497,10 @@ def parse_expr(self):
# condition: IF expr
# | empty
def parse_condition(self):
- if self.tok == TOK_IF:
- self.get_token()
- return self.parse_expr()
- else:
+ if self.tok != TOK_IF:
return None
+ self.get_token()
+ return self.parse_expr()
# property: DEFAULT y_or_n condition
# | DEPENDS ON expr
@@ -606,41 +605,41 @@ def scan_token(self, ch):
if ch == '#':
self.cursor = self.src.find('\n', self.cursor)
return None
- elif ch == '=':
+ if ch == '=':
return TOK_EQUAL
- elif ch == '(':
+ if ch == '(':
return TOK_LPAREN
- elif ch == ')':
+ if ch == ')':
return TOK_RPAREN
- elif ch == '&' and self.src[self.pos+1] == '&':
+ if ch == '&' and self.src[self.pos+1] == '&':
self.cursor += 1
return TOK_AND
- elif ch == '|' and self.src[self.pos+1] == '|':
+ if ch == '|' and self.src[self.pos+1] == '|':
self.cursor += 1
return TOK_OR
- elif ch == '!':
+ if ch == '!':
return TOK_NOT
- elif ch == 'd' and self.check_keyword("epends"):
+ if ch == 'd' and self.check_keyword("epends"):
return TOK_DEPENDS
- elif ch == 'o' and self.check_keyword("n"):
+ if ch == 'o' and self.check_keyword("n"):
return TOK_ON
- elif ch == 's' and self.check_keyword("elect"):
+ if ch == 's' and self.check_keyword("elect"):
return TOK_SELECT
- elif ch == 'i' and self.check_keyword("mply"):
+ if ch == 'i' and self.check_keyword("mply"):
return TOK_IMPLY
- elif ch == 'c' and self.check_keyword("onfig"):
+ if ch == 'c' and self.check_keyword("onfig"):
return TOK_CONFIG
- elif ch == 'd' and self.check_keyword("efault"):
+ if ch == 'd' and self.check_keyword("efault"):
return TOK_DEFAULT
- elif ch == 'b' and self.check_keyword("ool"):
+ if ch == 'b' and self.check_keyword("ool"):
return TOK_BOOL
- elif ch == 'i' and self.check_keyword("f"):
+ if ch == 'i' and self.check_keyword("f"):
return TOK_IF
- elif ch == 'y' and self.check_keyword(""):
+ if ch == 'y' and self.check_keyword(""):
return TOK_Y
- elif ch == 'n' and self.check_keyword(""):
+ if ch == 'n' and self.check_keyword(""):
return TOK_N
- elif (ch == 's' and self.check_keyword("ource")) or \
+ if (ch == 's' and self.check_keyword("ource")) or \
ch == 'i' and self.check_keyword("nclude"):
# source FILENAME
# include FILENAME
@@ -650,21 +649,23 @@ def scan_token(self, ch):
self.cursor = self.src.find('\n', self.cursor)
self.val = self.src[start:self.cursor]
return TOK_SOURCE
- elif ch.isalnum():
+ if ch.isalnum():
# identifier
while self.src[self.cursor].isalnum() or self.src[self.cursor] == '_':
self.cursor += 1
self.val = self.src[self.pos:self.cursor]
return TOK_ID
- elif ch == '\n':
+ if ch == '\n':
if self.cursor == len(self.src):
return TOK_EOF
self.line += 1
self.line_pos = self.cursor
- elif not ch.isspace():
- raise KconfigParserError(self, 'invalid input', ch)
+ return None
+ if ch.isspace():
+ return None
+
+ raise KconfigParserError(self, 'invalid input', ch)
- return None
def main() -> None:
argv = sys.argv
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 17/58] minikconf: add mypy annotations
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (14 preceding siblings ...)
2026-04-30 17:21 ` [PULL 16/58] minikconf: replace else with early return and avoid unnecessary else Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 18/58] hw/qdev: Clarify fallback order in qdev_get_printable_name() Paolo Bonzini
` (41 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Marc-André Lureau
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
python/tests/linters.py | 3 +
scripts/minikconf.py | 220 ++++++++++++++++++++++------------------
2 files changed, 122 insertions(+), 101 deletions(-)
diff --git a/python/tests/linters.py b/python/tests/linters.py
index 4ce4e8a9805..82b88aebb20 100644
--- a/python/tests/linters.py
+++ b/python/tests/linters.py
@@ -65,6 +65,9 @@ def test_isort_qapi_sphinx(self):
]
)
+ def test_mypy_minikconf(self):
+ check_call([sys.executable, "-m", "mypy", "../scripts/minikconf.py"])
+
def test_mypy_pkg(self):
check_call([sys.executable, "-m", "mypy", "-p", "qemu"])
diff --git a/scripts/minikconf.py b/scripts/minikconf.py
index 7f042daecbf..837c68a2469 100644
--- a/scripts/minikconf.py
+++ b/scripts/minikconf.py
@@ -17,31 +17,34 @@
import random
import re
import sys
+import typing as T
from dataclasses import dataclass
__all__ = [ 'KconfigDataError', 'KconfigParserError',
'KconfigData', 'KconfigParser' ,
'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ]
+Mangler = T.Callable[[bool], bool]
+
@dataclass
class IncludeInfo:
file: str
line: int
parent: IncludeInfo | None
- def __iter__(self):
- inf = self
+ def __iter__(self) -> T.Iterator[str]:
+ inf: IncludeInfo | None = self
while inf is not None:
yield "%s:%d" % (inf.file, inf.line)
inf = inf.parent
- def error_path(self):
+ def error_path(self) -> str:
res = ""
for loc in self:
res = "In file included from %s:\n" % loc + res
return res
-def debug_print(*args):
+def debug_print(*args: object) -> None:
#print('# ' + (' '.join(str(x) for x in args)))
pass
@@ -56,81 +59,81 @@ def debug_print(*args):
# -------------------------------------------
class KconfigDataError(Exception):
- def __init__(self, msg):
+ def __init__(self, msg: str) -> None:
self.msg = msg
- def __str__(self):
+ def __str__(self) -> str:
return self.msg
-allyesconfig = lambda x: True
-allnoconfig = lambda x: False
-defconfig = lambda x: x
-randconfig = lambda x: random.randint(0, 1) == 1
+allyesconfig: Mangler = lambda x: True
+allnoconfig: Mangler = lambda x: False
+defconfig: Mangler = lambda x: x
+randconfig: Mangler = lambda x: random.randint(0, 1) == 1
class KconfigData:
class Expr:
- def __and__(self, rhs):
+ def __and__(self, rhs: KconfigData.Expr) -> KconfigData.Expr:
return KconfigData.AND(self, rhs)
- def __or__(self, rhs):
+ def __or__(self, rhs: KconfigData.Expr) -> KconfigData.Expr:
return KconfigData.OR(self, rhs)
- def __invert__(self):
+ def __invert__(self) -> KconfigData.Expr:
return KconfigData.NOT(self)
# Abstract methods
- def add_edges_to(self, var):
+ def add_edges_to(self, var: KconfigData.Var) -> None:
pass
- def evaluate(self):
+ def evaluate(self) -> bool:
assert False
class AND(Expr):
- def __init__(self, lhs, rhs):
+ def __init__(self, lhs: KconfigData.Expr, rhs: KconfigData.Expr) -> None:
self.lhs = lhs
self.rhs = rhs
- def __str__(self):
+ def __str__(self) -> str:
return "(%s && %s)" % (self.lhs, self.rhs)
- def add_edges_to(self, var):
+ def add_edges_to(self, var: KconfigData.Var) -> None:
self.lhs.add_edges_to(var)
self.rhs.add_edges_to(var)
- def evaluate(self):
+ def evaluate(self) -> bool:
return self.lhs.evaluate() and self.rhs.evaluate()
class OR(Expr):
- def __init__(self, lhs, rhs):
+ def __init__(self, lhs: KconfigData.Expr, rhs: KconfigData.Expr) -> None:
self.lhs = lhs
self.rhs = rhs
- def __str__(self):
+ def __str__(self) -> str:
return "(%s || %s)" % (self.lhs, self.rhs)
- def add_edges_to(self, var):
+ def add_edges_to(self, var: KconfigData.Var) -> None:
self.lhs.add_edges_to(var)
self.rhs.add_edges_to(var)
- def evaluate(self):
+ def evaluate(self) -> bool:
return self.lhs.evaluate() or self.rhs.evaluate()
class NOT(Expr):
- def __init__(self, lhs):
+ def __init__(self, lhs: KconfigData.Expr) -> None:
self.lhs = lhs
- def __str__(self):
+ def __str__(self) -> str:
return "!%s" % (self.lhs)
- def add_edges_to(self, var):
+ def add_edges_to(self, var: KconfigData.Var) -> None:
self.lhs.add_edges_to(var)
- def evaluate(self):
+ def evaluate(self) -> bool:
return not self.lhs.evaluate()
class Var(Expr):
- def __init__(self, name):
+ def __init__(self, name: str) -> None:
self.name = name
- self.value = None
- self.outgoing = set()
- self.clauses_for_var = list()
- def __str__(self):
+ self.value: bool | None = None
+ self.outgoing: set[KconfigData.Var] = set()
+ self.clauses_for_var: list[KconfigData.Clause] = []
+ def __str__(self) -> str:
return self.name
- def has_value(self):
+ def has_value(self) -> bool:
return self.value is not None
- def set_value(self, val, clause):
+ def set_value(self, val: bool, clause: KconfigData.Clause) -> None:
self.clauses_for_var.append(clause)
if self.has_value() and self.value != val:
print("The following clauses were found for " + self.name, file=sys.stderr)
@@ -141,7 +144,8 @@ def set_value(self, val, clause):
self.value = val
# depth first search of the dependency graph
- def dfs(self, visited, f):
+ def dfs(self, visited: set[KconfigData.Var],
+ f: T.Callable[[KconfigData.Var], None]) -> None:
if self in visited:
return
visited.add(self)
@@ -149,87 +153,89 @@ def dfs(self, visited, f):
v.dfs(visited, f)
f(self)
- def add_edges_to(self, var):
+ def add_edges_to(self, var: KconfigData.Var) -> None:
self.outgoing.add(var)
- def evaluate(self):
+ def evaluate(self) -> bool:
if not self.has_value():
raise KconfigDataError('cycle found including %s' % self)
+ assert self.value is not None
return self.value
class Clause:
- def __init__(self, dest):
+ def __init__(self, dest: KconfigData.Var) -> None:
self.dest = dest
- def priority(self):
+ def priority(self) -> int:
return 0
- def process(self):
+ def process(self) -> None:
pass
class AssignmentClause(Clause):
- def __init__(self, dest, value):
+ def __init__(self, dest: KconfigData.Var, value: bool) -> None:
KconfigData.Clause.__init__(self, dest)
self.value = value
- def __str__(self):
+ def __str__(self) -> str:
return "CONFIG_%s=%s" % (self.dest, 'y' if self.value else 'n')
- def process(self):
+ def process(self) -> None:
self.dest.set_value(self.value, self)
class DefaultClause(Clause):
- def __init__(self, dest, value, cond=None):
+ def __init__(self, dest: KconfigData.Var, value: bool,
+ cond: KconfigData.Expr | None = None) -> None:
KconfigData.Clause.__init__(self, dest)
self.value = value
self.cond = cond
if self.cond is not None:
self.cond.add_edges_to(self.dest)
- def __str__(self):
+ def __str__(self) -> str:
value = 'y' if self.value else 'n'
if self.cond is None:
return "config %s default %s" % (self.dest, value)
else:
return "config %s default %s if %s" % (self.dest, value, self.cond)
- def priority(self):
+ def priority(self) -> int:
# Defaults are processed just before leaving the variable
return -1
- def process(self):
+ def process(self) -> None:
if not self.dest.has_value() and \
(self.cond is None or self.cond.evaluate()):
self.dest.set_value(self.value, self)
class DependsOnClause(Clause):
- def __init__(self, dest, expr):
+ def __init__(self, dest: KconfigData.Var, expr: KconfigData.Expr) -> None:
KconfigData.Clause.__init__(self, dest)
self.expr = expr
self.expr.add_edges_to(self.dest)
- def __str__(self):
+ def __str__(self) -> str:
return "config %s depends on %s" % (self.dest, self.expr)
- def process(self):
+ def process(self) -> None:
if not self.expr.evaluate():
self.dest.set_value(False, self)
class SelectClause(Clause):
- def __init__(self, dest, cond):
+ def __init__(self, dest: KconfigData.Var, cond: KconfigData.Expr) -> None:
KconfigData.Clause.__init__(self, dest)
self.cond = cond
self.cond.add_edges_to(self.dest)
- def __str__(self):
+ def __str__(self) -> str:
return "select %s if %s" % (self.dest, self.cond)
- def process(self):
+ def process(self) -> None:
if self.cond.evaluate():
self.dest.set_value(True, self)
- def __init__(self, value_mangler=defconfig):
+ def __init__(self, value_mangler: Mangler = defconfig) -> None:
self.value_mangler = value_mangler
- self.previously_included = []
- self.defined_vars = set()
- self.referenced_vars = dict()
- self.clauses = list()
+ self.previously_included: list[str] = []
+ self.defined_vars: set[str] = set()
+ self.referenced_vars: dict[str, KconfigData.Var] = {}
+ self.clauses: list[KconfigData.Clause] = []
# semantic analysis -------------
- def check_undefined(self):
+ def check_undefined(self) -> bool:
undef = False
for i in self.referenced_vars:
if i not in self.defined_vars:
@@ -237,7 +243,7 @@ def check_undefined(self):
undef = True
return undef
- def compute_config(self):
+ def compute_config(self) -> dict[str, bool]:
if self.check_undefined():
raise KconfigDataError("there were undefined symbols")
@@ -250,10 +256,10 @@ def compute_config(self):
debug_print(source, "->", [str(x) for x in edges.outgoing])
# The reverse of the depth-first order is the topological sort
- dfo = dict()
- visited = set()
+ dfo: dict[KconfigData.Var, int] = {}
+ visited: set[KconfigData.Var] = set()
debug_print("\n")
- def visit_fn(var):
+ def visit_fn(var: KconfigData.Var) -> None:
debug_print(var, "has DFS number", len(dfo))
dfo[var] = len(dfo)
@@ -272,7 +278,7 @@ def visit_fn(var):
clause.process()
debug_print("")
- values = dict()
+ values: dict[str, bool] = {}
for name, v in self.referenced_vars.items():
debug_print("Evaluating", name)
values[name] = v.evaluate()
@@ -281,39 +287,42 @@ def visit_fn(var):
# semantic actions -------------
- def do_declaration(self, var):
+ def do_declaration(self, var: KconfigData.Var) -> None:
if var.name in self.defined_vars:
raise KconfigDataError('variable "%s" defined twice' % var.name)
-
self.defined_vars.add(var.name)
# var is a string with the variable's name.
- def do_var(self, var):
+ def do_var(self, var: str) -> KconfigData.Var:
if var in self.referenced_vars:
return self.referenced_vars[var]
var_obj = self.referenced_vars[var] = KconfigData.Var(var)
return var_obj
- def do_assignment(self, var, val):
+ def do_assignment(self, var: KconfigData.Var, val: bool) -> None:
self.clauses.append(KconfigData.AssignmentClause(var, val))
- def do_cmdline_assignment(self, var, val):
+ def do_cmdline_assignment(self, var: str, val: bool) -> None:
assert var.startswith("CONFIG_")
self.do_assignment(self.do_var(var[7:]), val)
- def do_default(self, var, val, cond=None):
+ def do_default(self, var: KconfigData.Var, val: bool,
+ cond: KconfigData.Expr | None = None) -> None:
val = self.value_mangler(val)
self.clauses.append(KconfigData.DefaultClause(var, val, cond))
- def do_depends_on(self, var, expr):
+ def do_depends_on(self, var: KconfigData.Var,
+ expr: KconfigData.Expr) -> None:
self.clauses.append(KconfigData.DependsOnClause(var, expr))
- def do_select(self, var, symbol, cond=None):
+ def do_select(self, var: KconfigData.Var, symbol: KconfigData.Var,
+ cond: KconfigData.Expr | None = None) -> None:
cond = (cond & var) if cond is not None else var
self.clauses.append(KconfigData.SelectClause(symbol, cond))
- def do_imply(self, var, symbol, cond=None):
+ def do_imply(self, var: KconfigData.Var, symbol: KconfigData.Var,
+ cond: KconfigData.Expr | None = None) -> None:
# "config X imply Y [if COND]" is the same as
# "config Y default y if X [&& COND]"
cond = (cond & var) if cond is not None else var
@@ -325,7 +334,7 @@ def do_imply(self, var, symbol, cond=None):
# -------------------------------------------
# tokens table
-TOKENS = {}
+TOKENS: dict[int, str] = {}
TOK_NONE = -1
TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("'
TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"'
@@ -348,7 +357,8 @@ def do_imply(self, var, symbol, cond=None):
TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file'
class KconfigParserError(Exception):
- def __init__(self, parser, msg, tok=None):
+ def __init__(self, parser: KconfigParser, msg: str,
+ tok: int | str | None = None) -> None:
self.loc = parser.location()
tok = tok if tok is not None else parser.tok
if tok != TOK_NONE:
@@ -356,33 +366,37 @@ def __init__(self, parser, msg, tok=None):
msg = '%s before %s' % (msg, location)
self.msg = msg
- def __str__(self):
+ def __str__(self) -> str:
return "%s: %s" % (self.loc, self.msg)
class KconfigParser:
@classmethod
- def parse(cls, fp, data, incl_info=None):
+ def parse(cls, fp: T.TextIO, data: KconfigData, incl_info: IncludeInfo | None = None) -> None:
cls(fp, data, incl_info).parse_config()
- def __init__(self, fp, data, incl_info):
+ def __init__(self, fp: T.TextIO, data: KconfigData, incl_info: IncludeInfo | None = None):
self.data = data
self.incl_info = incl_info
self.abs_fname = os.path.abspath(fp.name)
self.fname = fp.name
self.data.previously_included.append(self.abs_fname)
+
src = fp.read()
if src == '' or src[-1] != '\n':
src += '\n'
self.src = src
- self.cursor = 0
- self.line = 1
- self.line_pos = 0
+ self.cursor: int = 0
+ self.line: int = 1
+ self.line_pos: int = 0
+ self.pos: int = 0
+ self.tok: int = TOK_NONE
+ self.val: str | None = None
self.get_token()
# file management -----
- def location(self):
+ def location(self) -> str:
col = 1
for ch in self.src[self.line_pos:self.pos]:
if ch == '\t':
@@ -393,7 +407,7 @@ def location(self):
incl_chain = inf.error_path() if inf is not None else ""
return '%s%s:%d:%d' % (incl_chain, self.fname, self.line, col)
- def do_include(self, include):
+ def do_include(self, include: str) -> None:
incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname),
include)
# catch inclusion cycle
@@ -421,7 +435,7 @@ def do_include(self, include):
# recursive descent parser -----
# y_or_n: Y | N
- def parse_y_or_n(self):
+ def parse_y_or_n(self) -> bool:
if self.tok == TOK_Y:
self.get_token()
return True
@@ -431,7 +445,7 @@ def parse_y_or_n(self):
raise KconfigParserError(self, 'Expected "y" or "n"')
# var: ID
- def parse_var(self):
+ def parse_var(self) -> KconfigData.Var:
if self.tok != TOK_ID:
raise KconfigParserError(self, 'Expected identifier')
val = self.val
@@ -440,7 +454,7 @@ def parse_var(self):
return self.data.do_var(val)
# assignment_var: ID (starting with "CONFIG_")
- def parse_assignment_var(self):
+ def parse_assignment_var(self) -> KconfigData.Var:
if self.tok != TOK_ID:
raise KconfigParserError(self, 'Expected identifier')
val = self.val
@@ -452,7 +466,7 @@ def parse_assignment_var(self):
return self.data.do_var(val[7:])
# assignment: var EQUAL y_or_n
- def parse_assignment(self):
+ def parse_assignment(self) -> None:
var = self.parse_assignment_var()
if self.tok != TOK_EQUAL:
raise KconfigParserError(self, 'Expected "="')
@@ -462,7 +476,7 @@ def parse_assignment(self):
# primary: NOT primary
# | LPAREN expr RPAREN
# | var
- def parse_primary(self):
+ def parse_primary(self) -> KconfigData.Expr:
if self.tok == TOK_NOT:
self.get_token()
val = ~self.parse_primary()
@@ -479,7 +493,7 @@ def parse_primary(self):
return val
# disj: primary (OR primary)*
- def parse_disj(self):
+ def parse_disj(self) -> KconfigData.Expr:
lhs = self.parse_primary()
while self.tok == TOK_OR:
self.get_token()
@@ -487,7 +501,7 @@ def parse_disj(self):
return lhs
# expr: disj (AND disj)*
- def parse_expr(self):
+ def parse_expr(self) -> KconfigData.Expr:
lhs = self.parse_disj()
while self.tok == TOK_AND:
self.get_token()
@@ -496,7 +510,7 @@ def parse_expr(self):
# condition: IF expr
# | empty
- def parse_condition(self):
+ def parse_condition(self) -> KconfigData.Expr | None:
if self.tok != TOK_IF:
return None
self.get_token()
@@ -506,7 +520,7 @@ def parse_condition(self):
# | DEPENDS ON expr
# | SELECT var condition
# | BOOL
- def parse_property(self, var):
+ def parse_property(self, var: KconfigData.Var) -> None:
if self.tok == TOK_DEFAULT:
self.get_token()
val = self.parse_y_or_n()
@@ -535,7 +549,7 @@ def parse_property(self, var):
# properties: properties property
# | /* empty */
- def parse_properties(self, var):
+ def parse_properties(self, var: KconfigData.Var) -> None:
while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \
self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
self.tok == TOK_IMPLY:
@@ -548,7 +562,7 @@ def parse_properties(self, var):
+ '"default", "depends on", "imply" or "select"')
# declaration: config var properties
- def parse_declaration(self):
+ def parse_declaration(self) -> None:
if self.tok == TOK_CONFIG:
self.get_token()
var = self.parse_var()
@@ -560,9 +574,10 @@ def parse_declaration(self):
# clause: SOURCE
# | declaration
# | assignment
- def parse_clause(self):
+ def parse_clause(self) -> None:
if self.tok == TOK_SOURCE:
val = self.val
+ assert val is not None
self.get_token()
self.do_include(val)
elif self.tok == TOK_CONFIG:
@@ -573,14 +588,15 @@ def parse_clause(self):
raise KconfigParserError(self, 'expected "source", "config" or identifier')
# config: clause+ EOF
- def parse_config(self):
+ def parse_config(self) -> KconfigData:
while self.tok != TOK_EOF:
self.parse_clause()
return self.data
# scanner -----
- def get_token(self):
+ def get_token(self) -> None:
+ assert self.src is not None
while True:
ch = self.src[self.cursor]
self.pos = self.cursor
@@ -592,7 +608,8 @@ def get_token(self):
self.tok = tok
return
- def check_keyword(self, rest):
+ def check_keyword(self, rest: str) -> bool:
+ assert self.src is not None
if not self.src.startswith(rest, self.cursor):
return False
length = len(rest)
@@ -601,7 +618,8 @@ def check_keyword(self, rest):
self.cursor += length
return True
- def scan_token(self, ch):
+ def scan_token(self, ch: str) -> int | None:
+ assert self.src is not None
if ch == '#':
self.cursor = self.src.find('\n', self.cursor)
return None
@@ -669,7 +687,7 @@ def scan_token(self, ch):
def main() -> None:
argv = sys.argv
- mode = defconfig
+ mode: Mangler = defconfig
if len(sys.argv) > 1:
if argv[1] == '--defconfig':
del argv[1]
@@ -693,7 +711,7 @@ def main() -> None:
sys.exit(1)
data = KconfigData(mode)
- external_vars = set()
+ external_vars: set[str] = set()
for arg in argv[3:]:
m = re.match(r'^(CONFIG_[A-Z0-9_]+)=([yn]?)$', arg)
if m is not None:
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 18/58] hw/qdev: Clarify fallback order in qdev_get_printable_name()
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (15 preceding siblings ...)
2026-04-30 17:21 ` [PULL 17/58] minikconf: add mypy annotations Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 19/58] hw/qdev: Prefix bus type in qdev_get_printable_name() device paths Paolo Bonzini
` (40 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Alessandro Ratti, Markus Armbruster
From: Alessandro Ratti <alessandro@0x65c.net>
Replace the uninformative "<unknown device>" final fallback with the
canonical QOM path (e.g. /machine/peripheral-anon/device[0]).
Also clean up comments to accurately describe qdev_get_dev_path()
behavior, drop an unnecessary comment on the dev->id check, and rename
the @vdev parameter to @dev for consistency with surrounding code.
Update the doc comment in qdev.h to reflect the new fallback chain.
Signed-off-by: Alessandro Ratti <alessandro@0x65c.net>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Link: https://lore.kernel.org/r/20260321100405.1525059-2-alessandro@0x65c.net
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/hw/core/qdev.h | 8 +++-----
hw/core/qdev.c | 26 +++++++-------------------
2 files changed, 10 insertions(+), 24 deletions(-)
diff --git a/include/hw/core/qdev.h b/include/hw/core/qdev.h
index 337d69ea2c3..226bd662901 100644
--- a/include/hw/core/qdev.h
+++ b/include/hw/core/qdev.h
@@ -1067,11 +1067,9 @@ char *qdev_get_dev_path(DeviceState *dev);
* user-facing error messages. The function will never return NULL,
* so the name can be used without further checking or fallbacks.
*
- * If the device has an explicitly set ID (e.g. by the user on the
- * command line via "-device thisdev,id=myid") this is preferred.
- * Otherwise we try the canonical QOM device path (which will be
- * the PCI ID for PCI devices, for example). If all else fails
- * we will return the placeholder "<unknown device">.
+ * Return the device's ID if it has one. Else, return the path of a
+ * device on its bus if it has one. Else return its canonical QOM
+ * path.
*/
const char *qdev_get_printable_name(DeviceState *dev);
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index b36101f3a75..78156185d57 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -412,33 +412,21 @@ char *qdev_get_dev_path(DeviceState *dev)
return NULL;
}
-const char *qdev_get_printable_name(DeviceState *vdev)
+const char *qdev_get_printable_name(DeviceState *dev)
{
- /*
- * Return device ID if explicity set
- * (e.g. -device virtio-blk-pci,id=foo)
- * This allows users to correlate errors with their custom device
- * names.
- */
- if (vdev->id) {
- return g_strdup(vdev->id);
+ if (dev->id) {
+ return g_strdup(dev->id);
}
/*
- * Fall back to the canonical QOM device path (eg. ID for PCI
- * devices).
- * This ensures the device is still uniquely and meaningfully
- * identified.
+ * Fall back to a bus-specific device path, if the bus
+ * provides one (e.g. PCI address "0000:00:04.0").
*/
- const char *path = qdev_get_dev_path(vdev);
+ const char *path = qdev_get_dev_path(dev);
if (path) {
return path;
}
- /*
- * Final fallback: if all else fails, return a placeholder string.
- * This ensures the error message always contains a valid string.
- */
- return g_strdup("<unknown device>");
+ return object_get_canonical_path(OBJECT(dev));
}
void qdev_add_unplug_blocker(DeviceState *dev, Error *reason)
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 19/58] hw/qdev: Prefix bus type in qdev_get_printable_name() device paths
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (16 preceding siblings ...)
2026-04-30 17:21 ` [PULL 18/58] hw/qdev: Clarify fallback order in qdev_get_printable_name() Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 20/58] hw/qdev: Consolidate qdev_get_printable_name() into qdev_get_human_name() Paolo Bonzini
` (39 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Alessandro Ratti, Markus Armbruster
From: Alessandro Ratti <alessandro@0x65c.net>
Raw get_dev_path() output (e.g. "0000:00:04.0", "/1") is ambiguous
without knowing which bus produced it. Prefix the path with the bus
type name so error messages become self-describing.
Examples:
- PCIE device 0000:00:04.0
- virtio-pci-bus device 0000:00:03.0
Suggested-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Alessandro Ratti <alessandro@0x65c.net>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Link: https://lore.kernel.org/r/20260321100405.1525059-3-alessandro@0x65c.net
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
hw/core/qdev.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index 78156185d57..0efc83f6740 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -419,11 +419,13 @@ const char *qdev_get_printable_name(DeviceState *dev)
}
/*
* Fall back to a bus-specific device path, if the bus
- * provides one (e.g. PCI address "0000:00:04.0").
+ * provides one (e.g. "PCI device 0000:00:04.0").
*/
- const char *path = qdev_get_dev_path(dev);
+ g_autofree char *path = qdev_get_dev_path(dev);
if (path) {
- return path;
+ const char *bus_type = object_get_typename(OBJECT(dev->parent_bus));
+ char *name = g_strdup_printf("%s device %s", bus_type, path);
+ return name;
}
return object_get_canonical_path(OBJECT(dev));
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 20/58] hw/qdev: Consolidate qdev_get_printable_name() into qdev_get_human_name()
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (17 preceding siblings ...)
2026-04-30 17:21 ` [PULL 19/58] hw/qdev: Prefix bus type in qdev_get_printable_name() device paths Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 21/58] target/i386: add new AMD EPYC models for GMET enablement Paolo Bonzini
` (38 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Alessandro Ratti, Peter Maydell, Markus Armbruster
From: Alessandro Ratti <alessandro@0x65c.net>
Rename qdev_get_printable_name() to qdev_get_human_name(), remove
the old qdev_get_human_name() implementation, and switch the three
qdev_get_printable_name() callers in hw/virtio/virtio.c.
qdev_get_printable_name() subsumes qdev_get_human_name(): both
return the device ID when set and fall back to the canonical QOM
path, but qdev_get_printable_name() also tries the bus-specific
path first, providing more informative output.
Suggested-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Alessandro Ratti <alessandro@0x65c.net>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Link: https://lore.kernel.org/r/20260321100405.1525059-4-alessandro@0x65c.net
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/hw/core/qdev.h | 26 +++++---------------------
hw/core/qdev.c | 10 +---------
hw/virtio/virtio.c | 6 +++---
3 files changed, 9 insertions(+), 33 deletions(-)
diff --git a/include/hw/core/qdev.h b/include/hw/core/qdev.h
index 226bd662901..e1476223411 100644
--- a/include/hw/core/qdev.h
+++ b/include/hw/core/qdev.h
@@ -1022,13 +1022,12 @@ Object *machine_get_container(const char *name);
* qdev_get_human_name() - Return a human-readable name for a device
* @dev: The device. Must be a valid and non-NULL pointer.
*
- * .. note::
- * This function is intended for user friendly error messages.
+ * Returns: A newly allocated string suitable for user-facing error
+ * messages.
*
- * Returns: A newly allocated string containing the device id if not null,
- * else the object canonical path.
- *
- * Use g_free() to free it.
+ * Return the device's ID if it has one. Else, return the path of a
+ * device on its bus if it has one. Else return its canonical QOM
+ * path.
*/
char *qdev_get_human_name(DeviceState *dev);
@@ -1058,21 +1057,6 @@ extern bool qdev_hot_removed;
*/
char *qdev_get_dev_path(DeviceState *dev);
-/**
- * qdev_get_printable_name: Return human readable name for device
- * @dev: Device to get name of
- *
- * Returns: A newly allocated string containing some human
- * readable name for the device, suitable for printing in
- * user-facing error messages. The function will never return NULL,
- * so the name can be used without further checking or fallbacks.
- *
- * Return the device's ID if it has one. Else, return the path of a
- * device on its bus if it has one. Else return its canonical QOM
- * path.
- */
-const char *qdev_get_printable_name(DeviceState *dev);
-
void qbus_set_hotplug_handler(BusState *bus, Object *handler);
void qbus_set_bus_hotplug_handler(BusState *bus);
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index 0efc83f6740..e2aab3d1fc6 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -412,7 +412,7 @@ char *qdev_get_dev_path(DeviceState *dev)
return NULL;
}
-const char *qdev_get_printable_name(DeviceState *dev)
+char *qdev_get_human_name(DeviceState *dev)
{
if (dev->id) {
return g_strdup(dev->id);
@@ -858,14 +858,6 @@ Object *machine_get_container(const char *name)
return container;
}
-char *qdev_get_human_name(DeviceState *dev)
-{
- g_assert(dev != NULL);
-
- return dev->id ?
- g_strdup(dev->id) : object_get_canonical_path(OBJECT(dev));
-}
-
static MachineInitPhase machine_phase;
bool phase_check(MachineInitPhase phase)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index 8fcf6cfd0b4..63e2faee994 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -281,7 +281,7 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n)
len = address_space_cache_init(&new->desc, vdev->dma_as,
addr, size, packed);
if (len < size) {
- g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev));
+ g_autofree char *devname = qdev_get_human_name(DEVICE(vdev));
virtio_error(vdev,
"Failed to map descriptor ring for device %s: "
@@ -294,7 +294,7 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n)
len = address_space_cache_init(&new->used, vdev->dma_as,
vq->vring.used, size, true);
if (len < size) {
- g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev));
+ g_autofree char *devname = qdev_get_human_name(DEVICE(vdev));
virtio_error(vdev,
"Failed to map used ring for device %s: "
@@ -307,7 +307,7 @@ void virtio_init_region_cache(VirtIODevice *vdev, int n)
len = address_space_cache_init(&new->avail, vdev->dma_as,
vq->vring.avail, size, false);
if (len < size) {
- g_autofree const char *devname = qdev_get_printable_name(DEVICE(vdev));
+ g_autofree char *devname = qdev_get_human_name(DEVICE(vdev));
virtio_error(vdev,
"Failed to map avalaible ring for device %s: "
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 21/58] target/i386: add new AMD EPYC models for GMET enablement
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (18 preceding siblings ...)
2026-04-30 17:21 ` [PULL 20/58] hw/qdev: Consolidate qdev_get_printable_name() into qdev_get_human_name() Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 22/58] target/i386: add new Intel models for MMIO/GDS/RFDS mitigation status Paolo Bonzini
` (37 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jon Kohler, Babu Moger, Nikunj A Dadhania
From: Jon Kohler <jon@nutanix.com>
Add models to expose Guest Mode Execute Trap (GMET) support,
and add CPUID_SVM_GMET header definition so that future EPYC
models can easily add default support.
New models are:
AMD EPYC-Milan-v4
AMD EPYC-Genoa-v3
AMD EPYC-Turin-v2
Cc: Babu Moger <babu.moger@amd.com>
Cc: Nikunj A Dadhania <nikunj@amd.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Jon Kohler <jon@nutanix.com>
Link: https://lore.kernel.org/r/20260330193428.1663253-2-jon@nutanix.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/cpu.h | 1 +
target/i386/cpu.c | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 35 insertions(+)
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 0b539155c40..6401028e70d 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -879,6 +879,7 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w);
#define CPUID_SVM_AVIC (1U << 13)
#define CPUID_SVM_V_VMSAVE_VMLOAD (1U << 15)
#define CPUID_SVM_VGIF (1U << 16)
+#define CPUID_SVM_GMET (1U << 17)
#define CPUID_SVM_VNMI (1U << 25)
#define CPUID_SVM_SVME_ADDR_CHK (1U << 28)
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index 9d126600c05..b19bdf99792 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -6836,6 +6836,16 @@ static const X86CPUDefinition builtin_x86_defs[] = {
},
.cache_info = &epyc_milan_v3_cache_info
},
+ {
+ .version = 4,
+ .props = (PropValue[]) {
+ { "gmet", "on" },
+ { "model-id",
+ "AMD EPYC-Milan-v4 Processor" },
+ { /* end of list */ }
+ },
+ .cache_info = &epyc_milan_v3_cache_info
+ },
{ /* end of list */ }
}
},
@@ -6933,6 +6943,16 @@ static const X86CPUDefinition builtin_x86_defs[] = {
},
.cache_info = &epyc_genoa_v2_cache_info
},
+ {
+ .version = 3,
+ .props = (PropValue[]) {
+ { "gmet", "on" },
+ { "model-id",
+ "AMD EPYC-Genoa-v3 Processor" },
+ { /* end of list */ }
+ },
+ .cache_info = &epyc_genoa_v2_cache_info
+ },
{ /* end of list */ }
}
},
@@ -7163,6 +7183,20 @@ static const X86CPUDefinition builtin_x86_defs[] = {
.xlevel = 0x80000022,
.model_id = "AMD EPYC-Turin Processor",
.cache_info = &epyc_turin_cache_info,
+ .versions = (X86CPUVersionDefinition[]) {
+ { .version = 1 },
+ {
+ .version = 2,
+ .props = (PropValue[]) {
+ { "gmet", "on" },
+ { "model-id",
+ "AMD EPYC-Turin-v2 Processor" },
+ { /* end of list */ }
+ },
+ .cache_info = &epyc_turin_cache_info
+ },
+ { /* end of list */ }
+ }
},
};
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 22/58] target/i386: add new Intel models for MMIO/GDS/RFDS mitigation status
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (19 preceding siblings ...)
2026-04-30 17:21 ` [PULL 21/58] target/i386: add new AMD EPYC models for GMET enablement Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 23/58] target/i386: add new Intel models for MBEC enablement Paolo Bonzini
` (36 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jon Kohler, Pawan Gupta, Zhao Liu
From: Jon Kohler <jon@nutanix.com>
Add new bits to ARCH_CAPABILITIES MSR to enumerate the status of the
MMIO/GDS/RFDS mitigations on Cascade Lake, Ice Lake, Sapphire Rapids,
and Granite Rapids processors.
These have been advertised in Intel microcode updates for a while now,
but require user space to opt in to advertise them to guests.
New models are:
- Cascadelake-Server-v6
- Icelake-Server-v8
- SapphireRapids-v7 (note, already got MMIO fixes on commit [1])
- GraniteRapids-v6
[1] 3baf7ae63505 ("target/i386: Add few security fix bits in
ARCH_CAPABILITIES into SapphireRapids CPU model")
Cc: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Cc: Zhao Liu <zhao1.liu@intel.com>
Signed-off-by: Jon Kohler <jon@nutanix.com>
Link: https://lore.kernel.org/r/20260330193428.1663253-3-jon@nutanix.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/cpu.c | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index b19bdf99792..196665d83c7 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -4901,6 +4901,17 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ }
},
},
+ { .version = 6,
+ .note = "with MMIO/GDS/RFDS mitigation status",
+ .props = (PropValue[]) {
+ { "fb-clear", "on" },
+ { "gds-no", "on" },
+ { "psdp-no", "on" },
+ { "rfds-no", "on" },
+ { "sbdr-ssdp-no", "on" },
+ { /* end of list */ }
+ },
+ },
{ /* end of list */ }
}
},
@@ -5183,6 +5194,18 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ }
},
},
+ {
+ .version = 8,
+ .note = "with MMIO/GDS/RFDS mitigation status",
+ .props = (PropValue[]) {
+ { "fb-clear", "on" },
+ { "gds-no", "on" },
+ { "psdp-no", "on" },
+ { "rfds-no", "on" },
+ { "sbdr-ssdp-no", "on" },
+ { /* end of list */ }
+ },
+ },
{ /* end of list */ }
}
},
@@ -5360,6 +5383,15 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ },
}
},
+ {
+ .version = 7,
+ .note = "with GDS and RFDS mitigation status",
+ .props = (PropValue[]) {
+ { "gds-no", "on" },
+ { "rfds-no", "on" },
+ { /* end of list */ },
+ }
+ },
{ /* end of list */ }
}
},
@@ -5541,6 +5573,15 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ },
}
},
+ {
+ .version = 6,
+ .note = "with GDS and RFDS mitigation status",
+ .props = (PropValue[]) {
+ { "gds-no", "on" },
+ { "rfds-no", "on" },
+ { /* end of list */ },
+ }
+ },
{ /* end of list */ },
},
},
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 23/58] target/i386: add new Intel models for MBEC enablement
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (20 preceding siblings ...)
2026-04-30 17:21 ` [PULL 22/58] target/i386: add new Intel models for MMIO/GDS/RFDS mitigation status Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 24/58] whpx: i386: x2apic emulation Paolo Bonzini
` (35 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Jon Kohler, Zhao Liu
From: Jon Kohler <jon@nutanix.com>
Add models to expose Mode Based Execute Control (MBEC) support, which
is a Skylake++ feature.
Note: Future models can use VMX_SECONDARY_EXEC_MODE_BASED_EPT_EXEC to
easily add default support.
New models are:
- Skylake-Server-v6
- Cascadelake-Server-v7
- Icelake-Server-v9
- SapphireRapids-v8
- GraniteRapids-v7
- DiamondRapids-v2
- SierraForest-v6
- ClearwaterForest-v4
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Zhao Liu <zhao1.liu@intel.com>
Signed-off-by: Jon Kohler <jon@nutanix.com>
Link: https://lore.kernel.org/r/20260330193428.1663253-4-jon@nutanix.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/cpu.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index 196665d83c7..f4856c61044 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -4767,6 +4767,14 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ }
}
},
+ {
+ .version = 6,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ }
+ }
+ },
{ /* end of list */ }
}
},
@@ -4912,6 +4920,13 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ }
},
},
+ { .version = 7,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ }
+ },
+ },
{ /* end of list */ }
}
},
@@ -5206,6 +5221,14 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ }
},
},
+ {
+ .version = 9,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ }
+ }
+ },
{ /* end of list */ }
}
},
@@ -5392,6 +5415,14 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ },
}
},
+ {
+ .version = 8,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ },
+ }
+ },
{ /* end of list */ }
}
},
@@ -5582,6 +5613,14 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ },
}
},
+ {
+ .version = 7,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ },
+ }
+ },
{ /* end of list */ },
},
},
@@ -5776,6 +5815,18 @@ static const X86CPUDefinition builtin_x86_defs[] = {
.features[FEAT_VMX_VMFUNC] = MSR_VMX_VMFUNC_EPT_SWITCHING,
.xlevel = 0x80000008,
.model_id = "Intel Xeon Processor (DiamondRapids)",
+ .versions = (X86CPUVersionDefinition[]) {
+ { .version = 1 },
+ {
+ .version = 2,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ },
+ }
+ },
+ { /* end of list */ },
+ },
},
{
.name = "SierraForest",
@@ -5947,6 +5998,14 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ },
}
},
+ {
+ .version = 6,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ },
+ }
+ },
{ /* end of list */ },
},
},
@@ -6103,6 +6162,14 @@ static const X86CPUDefinition builtin_x86_defs[] = {
{ /* end of list */ },
}
},
+ {
+ .version = 4,
+ .note = "with MBEC enabled",
+ .props = (PropValue[]) {
+ { "vmx-mbec", "on" },
+ { /* end of list */ },
+ }
+ },
{ /* end of list */ },
},
},
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 24/58] whpx: i386: x2apic emulation
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (21 preceding siblings ...)
2026-04-30 17:21 ` [PULL 23/58] target/i386: add new Intel models for MBEC enablement Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 25/58] whpx: i386: wire up feature probing Paolo Bonzini
` (34 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Add x2apic emulation to WHPX for the kernel-irqchip=off case.
Unfortunately, it looks like there isn't a workaround available
for proper behavior of PIC interrupts when kernel-irqchip=on
for Windows 10. The OS is out of support outside of extended
security updates so this will not be addressed.
The performance boost is quite visible for multicore guests.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-3-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 134 +++++++++++++++++++++++++++++++++++-
1 file changed, 133 insertions(+), 1 deletion(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index e56ae2b3433..4127440c0ca 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -1082,6 +1082,8 @@ HRESULT whpx_set_exception_exit_bitmap(UINT64 exceptions)
/* Register for MSR and CPUID exits */
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
prop.ExtendedVmExits.X64MsrExit = 1;
+ prop.ExtendedVmExits.X64CpuidExit = 1;
+
if (exceptions != 0) {
prop.ExtendedVmExits.ExceptionExit = 1;
}
@@ -1898,6 +1900,18 @@ int whpx_vcpu_run(CPUState *cpu)
WHV_REGISTER_NAME reg_names[3];
UINT32 reg_count;
bool is_known_msr = 0;
+ uint64_t val;
+
+ if (vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
+ val = ((uint32_t)vcpu->exit_ctx.MsrAccess.Rax) |
+ ((uint64_t)(vcpu->exit_ctx.MsrAccess.Rdx) << 32);
+ } else {
+ /*
+ * Workaround for [-Werror=maybe-uninitialized]
+ * with GCC. Not needed with Clang.
+ */
+ val = 0;
+ }
reg_names[0] = WHvX64RegisterRip;
reg_names[1] = WHvX64RegisterRax;
@@ -1911,7 +1925,47 @@ int whpx_vcpu_run(CPUState *cpu)
&& !vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite
&& !whpx_irqchip_in_kernel()) {
is_known_msr = 1;
- reg_values[1].Reg32 = (uint32_t)X86_CPU(cpu)->env.apic_bus_freq;
+ val = X86_CPU(cpu)->env.apic_bus_freq;
+ }
+
+ if (!whpx_irqchip_in_kernel() &&
+ vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
+ is_known_msr = 1;
+ if (!vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
+ /* Read path unreachable on Hyper-V */
+ abort();
+ } else {
+ WHV_REGISTER_VALUE reg = {.Reg64 = val};
+ int msr_ret = cpu_set_apic_base(X86_CPU(cpu)->apic_state, val);
+ if (msr_ret < 0) {
+ x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ }
+ whpx_set_reg(cpu, WHvX64RegisterApicBase, reg);
+ }
+ }
+
+ if (!whpx_irqchip_in_kernel() &&
+ vcpu->exit_ctx.MsrAccess.MsrNumber >= MSR_APIC_START &&
+ vcpu->exit_ctx.MsrAccess.MsrNumber <= MSR_APIC_END) {
+ int index = vcpu->exit_ctx.MsrAccess.MsrNumber - MSR_APIC_START;
+ int msr_ret;
+ is_known_msr = 1;
+ if (!vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
+ bql_lock();
+ msr_ret = apic_msr_read(X86_CPU(cpu)->apic_state, index, &val);
+ bql_unlock();
+ reg_values[1].Reg64 = val;
+ if (msr_ret < 0) {
+ x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ }
+ } else {
+ bql_lock();
+ msr_ret = apic_msr_write(X86_CPU(cpu)->apic_state, index, val);
+ bql_unlock();
+ if (msr_ret < 0) {
+ x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ }
+ }
}
/*
* For all unsupported MSR access we:
@@ -1921,6 +1975,11 @@ int whpx_vcpu_run(CPUState *cpu)
reg_count = vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite ?
1 : 3;
+ if (!vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
+ reg_values[1].Reg32 = (uint32_t)val;
+ reg_values[2].Reg32 = (uint32_t)(val >> 32);
+ }
+
if (!is_known_msr) {
trace_whpx_unsupported_msr_access(vcpu->exit_ctx.MsrAccess.MsrNumber,
vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite);
@@ -1939,6 +1998,47 @@ int whpx_vcpu_run(CPUState *cpu)
ret = 0;
break;
}
+ case WHvRunVpExitReasonX64Cpuid: {
+ WHV_REGISTER_VALUE reg_values[5] = {0};
+ WHV_REGISTER_NAME reg_names[5];
+ UINT32 reg_count = 5;
+ X86CPU *x86_cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86_cpu->env;
+
+ reg_names[0] = WHvX64RegisterRip;
+ reg_names[1] = WHvX64RegisterRax;
+ reg_names[2] = WHvX64RegisterRcx;
+ reg_names[3] = WHvX64RegisterRdx;
+ reg_names[4] = WHvX64RegisterRbx;
+
+ reg_values[0].Reg64 =
+ vcpu->exit_ctx.VpContext.Rip +
+ vcpu->exit_ctx.VpContext.InstructionLength;
+
+ reg_values[1].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRax;
+ reg_values[2].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRcx;
+ reg_values[3].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRdx;
+ reg_values[4].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRbx;
+
+ if (vcpu->exit_ctx.CpuidAccess.Rax == 1) {
+ if (cpu_has_x2apic_feature(env)) {
+ reg_values[2].Reg64 |= CPUID_EXT_X2APIC;
+ }
+ }
+
+ hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
+ whpx->partition,
+ cpu->cpu_index,
+ reg_names, reg_count,
+ reg_values);
+
+ if (FAILED(hr)) {
+ error_report("WHPX: Failed to set CpuidAccess state "
+ " registers, hr=%08lx", hr);
+ }
+ ret = 0;
+ break;
+ }
case WHvRunVpExitReasonException:
whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE);
@@ -2136,6 +2236,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
WHV_PROCESSOR_FEATURES_BANKS processor_features;
WHV_PROCESSOR_PERFMON_FEATURES perfmon_features;
bool is_legacy_os = false;
+ UINT32 cpuidExitList[] = {1};
whpx = &whpx_global;
@@ -2354,6 +2455,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
/* Register for MSR and CPUID exits */
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
prop.ExtendedVmExits.X64MsrExit = 1;
+ prop.ExtendedVmExits.X64CpuidExit = 1;
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
@@ -2366,6 +2468,36 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
goto error;
}
+ memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
+ prop.X64MsrExitBitmap.UnhandledMsrs = 1;
+ if (!whpx_irqchip_in_kernel()) {
+ prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
+ }
+
+ hr = whp_dispatch.WHvSetPartitionProperty(
+ whpx->partition,
+ WHvPartitionPropertyCodeX64MsrExitBitmap,
+ &prop,
+ sizeof(WHV_PARTITION_PROPERTY));
+ if (FAILED(hr)) {
+ error_report("WHPX: Failed to set MSR exit bitmap, hr=%08lx", hr);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ hr = whp_dispatch.WHvSetPartitionProperty(
+ whpx->partition,
+ WHvPartitionPropertyCodeCpuidExitList,
+ cpuidExitList,
+ RTL_NUMBER_OF(cpuidExitList) * sizeof(UINT32));
+
+ if (FAILED(hr)) {
+ error_report("WHPX: Failed to set partition CpuidExitList hr=%08lx",
+ hr);
+ ret = -EINVAL;
+ goto error;
+ }
+
/*
* We do not want to intercept any exceptions from the guest,
* until we actually start debugging with gdb.
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 25/58] whpx: i386: wire up feature probing
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (22 preceding siblings ...)
2026-04-30 17:21 ` [PULL 24/58] whpx: i386: x2apic emulation Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 26/58] whpx: i386: disable TbFlushHypercalls for emulated LAPIC Paolo Bonzini
` (33 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Windows 10 doesn't have the API for this, so using this
only for Windows 11.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-4-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-internal.h | 9 ++
target/i386/whpx/whpx-i386.h | 12 ++
target/i386/cpu.c | 17 +++
target/i386/whpx/whpx-all.c | 165 +++++++++++++++++++++++++++-
target/i386/whpx/whpx-cpu-legacy.c | 171 +++++++++++++++++++++++++++++
target/i386/whpx/meson.build | 1 +
6 files changed, 370 insertions(+), 5 deletions(-)
create mode 100644 target/i386/whpx/whpx-i386.h
create mode 100644 target/i386/whpx/whpx-cpu-legacy.c
diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h
index 8482901f714..5902124b637 100644
--- a/include/system/whpx-internal.h
+++ b/include/system/whpx-internal.h
@@ -73,6 +73,14 @@ void whpx_apic_get(APICCommonState *s);
X(HRESULT, WHvGetVirtualProcessorRegisters, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, const WHV_REGISTER_NAME* RegisterNames, UINT32 RegisterCount, WHV_REGISTER_VALUE* RegisterValues)) \
X(HRESULT, WHvSetVirtualProcessorRegisters, (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, const WHV_REGISTER_NAME* RegisterNames, UINT32 RegisterCount, const WHV_REGISTER_VALUE* RegisterValues)) \
+#ifdef __x86_64__
+#define LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL_ARCH(X) \
+ X(HRESULT, WHvGetVirtualProcessorCpuidOutput, \
+ (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, UINT32 Eax, \
+ UINT32 Ecx, WHV_CPUID_OUTPUT *CpuidOutput))
+#else
+#define LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL_ARCH(X)
+#endif
/*
* These are supplemental functions that may not be present
* on all versions and are not critical for basic functionality.
@@ -89,6 +97,7 @@ void whpx_apic_get(APICCommonState *s);
UINT32 StateSize)) \
X(HRESULT, WHvResetPartition, \
(WHV_PARTITION_HANDLE Partition)) \
+ LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL_ARCH(X)
#define WHP_DEFINE_TYPE(return_type, function_name, signature) \
typedef return_type (WINAPI *function_name ## _t) signature;
diff --git a/target/i386/whpx/whpx-i386.h b/target/i386/whpx/whpx-i386.h
new file mode 100644
index 00000000000..a1cf7328623
--- /dev/null
+++ b/target/i386/whpx/whpx-i386.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+uint32_t whpx_get_supported_cpuid(uint32_t func, uint32_t idx, int reg);
+uint64_t whpx_get_supported_msr_feature(uint32_t index);
+bool whpx_is_legacy_os(void);
+
+uint32_t whpx_get_supported_cpuid_legacy(uint32_t func, uint32_t idx,
+ int reg);
+bool whpx_has_xsave(void);
+bool whpx_has_xsaves(void);
+bool whpx_has_rdtscp(void);
+bool whpx_has_invpcid(void);
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index f4856c61044..7ea80f07c7c 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -26,6 +26,8 @@
#include "tcg/helper-tcg.h"
#include "exec/translation-block.h"
#include "system/hvf.h"
+#include "system/whpx.h"
+#include "whpx/whpx-i386.h"
#include "hvf/hvf-i386.h"
#include "kvm/kvm_i386.h"
#include "kvm/tdx.h"
@@ -8230,6 +8232,16 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w)
r = hvf_get_supported_cpuid(wi->cpuid.eax,
wi->cpuid.ecx,
wi->cpuid.reg);
+ } else if (whpx_enabled()) {
+ switch (wi->type) {
+ case CPUID_FEATURE_WORD:
+ r = whpx_get_supported_cpuid(wi->cpuid.eax, wi->cpuid.ecx,
+ wi->cpuid.reg);
+ break;
+ case MSR_FEATURE_WORD:
+ r = whpx_get_supported_msr_feature(wi->msr.index);
+ break;
+ }
} else if (tcg_enabled() || qtest_enabled()) {
r = wi->tcg_features;
} else {
@@ -8311,6 +8323,11 @@ static void x86_cpu_get_supported_cpuid(uint32_t func, uint32_t index,
*ebx = hvf_get_supported_cpuid(func, index, R_EBX);
*ecx = hvf_get_supported_cpuid(func, index, R_ECX);
*edx = hvf_get_supported_cpuid(func, index, R_EDX);
+ } else if (whpx_enabled()) {
+ *eax = whpx_get_supported_cpuid(func, index, R_EAX);
+ *ebx = whpx_get_supported_cpuid(func, index, R_EBX);
+ *ecx = whpx_get_supported_cpuid(func, index, R_ECX);
+ *edx = whpx_get_supported_cpuid(func, index, R_EDX);
} else {
*eax = 0;
*ebx = 0;
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 4127440c0ca..d211b3f2efa 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -36,6 +36,7 @@
#include "system/whpx-accel-ops.h"
#include "system/whpx-all.h"
#include "system/whpx-common.h"
+#include "whpx-i386.h"
#include "emulate/x86_decode.h"
#include "emulate/x86_emu.h"
@@ -49,6 +50,8 @@
/* for kernel-irqchip=off */
#define HV_X64_MSR_APIC_FREQUENCY 0x40000023
+static bool is_modern_os = true;
+
static const WHV_REGISTER_NAME whpx_register_names[] = {
/* X64 General purpose registers */
@@ -265,11 +268,30 @@ typedef enum WhpxStepMode {
static uint32_t max_vcpu_index;
static WHV_PROCESSOR_XSAVE_FEATURES whpx_xsave_cap;
-static bool whpx_has_xsave(void)
+bool whpx_has_xsave(void)
{
return whpx_xsave_cap.XsaveSupport;
}
+bool whpx_has_xsaves(void)
+{
+ return whpx_xsave_cap.XsaveSupervisorSupport;
+}
+
+static bool whpx_rdtsc_cap;
+
+bool whpx_has_rdtscp(void)
+{
+ return whpx_rdtsc_cap;
+}
+
+static bool whpx_invpcid_cap;
+
+bool whpx_has_invpcid(void)
+{
+ return whpx_invpcid_cap;
+}
+
static WHV_X64_SEGMENT_REGISTER whpx_seg_q2h(const SegmentCache *qs, int v86,
int r86)
{
@@ -1062,6 +1084,137 @@ static void whpx_init_emu(void)
init_emu(&whpx_x86_emul_ops);
}
+bool whpx_is_legacy_os(void)
+{
+ return !is_modern_os;
+}
+
+uint32_t whpx_get_supported_cpuid(uint32_t func, uint32_t idx, int reg)
+{
+ WHV_CPUID_OUTPUT output = {};
+ uint32_t eax, ebx, ecx, edx;
+ uint32_t cpu_index = 0;
+ bool temp_cpu = true;
+ HRESULT hr;
+
+ /* Legacy OSes don't have WHvGetVirtualProcessorCpuidOutput */
+ if (whpx_is_legacy_os()) {
+ return whpx_get_supported_cpuid_legacy(func, idx, reg);
+ }
+
+ hr = whp_dispatch.WHvCreateVirtualProcessor(
+ whpx_global.partition, cpu_index, 0);
+
+ /* This means that the CPU already exists... */
+ if (FAILED(hr)) {
+ temp_cpu = false;
+ }
+
+ hr = whp_dispatch.WHvGetVirtualProcessorCpuidOutput(whpx_global.partition,
+ cpu_index, func, idx, &output);
+
+ if (FAILED(hr)) {
+ abort();
+ }
+
+ if (temp_cpu) {
+ hr = whp_dispatch.WHvDeleteVirtualProcessor(whpx_global.partition, cpu_index);
+ if (FAILED(hr)) {
+ abort();
+ }
+ }
+
+ eax = output.Eax;
+ ebx = output.Ebx;
+ ecx = output.Ecx;
+ edx = output.Edx;
+
+ /*
+ * We can emulate X2APIC even for the kernel-irqchip=off case.
+ * CPUID_EXT_HYPERVISOR and CPUID_HT should be considered present
+ * always, so report them as unconditionally supported here.
+ */
+ if (func == 1) {
+ ecx |= CPUID_EXT_X2APIC;
+ ecx |= CPUID_EXT_HYPERVISOR;
+ edx |= CPUID_HT;
+ }
+
+ switch (reg) {
+ case R_EAX:
+ return eax;
+ case R_EBX:
+ return ebx;
+ case R_ECX:
+ return ecx;
+ case R_EDX:
+ return edx;
+ default:
+ return 0;
+ }
+}
+
+uint64_t whpx_get_supported_msr_feature(uint32_t index)
+{
+ WHV_CAPABILITY_CODE cap;
+ uint64_t val = 0;
+
+ switch (index) {
+ case MSR_IA32_VMX_BASIC:
+ cap = WHvCapabilityCodeVmxBasic;
+ break;
+ case MSR_IA32_VMX_MISC:
+ cap = WHvCapabilityCodeVmxMisc;
+ break;
+ case MSR_IA32_VMX_CR0_FIXED0:
+ cap = WHvCapabilityCodeVmxCr0Fixed0;
+ break;
+ case MSR_IA32_VMX_CR0_FIXED1:
+ cap = WHvCapabilityCodeVmxCr0Fixed1;
+ break;
+ case MSR_IA32_VMX_CR4_FIXED0:
+ cap = WHvCapabilityCodeVmxCr4Fixed0;
+ break;
+ case MSR_IA32_VMX_CR4_FIXED1:
+ cap = WHvCapabilityCodeVmxCr4Fixed1;
+ break;
+ case MSR_IA32_VMX_VMCS_ENUM:
+ cap = WHvCapabilityCodeVmxVmcsEnum;
+ break;
+ case MSR_IA32_VMX_PROCBASED_CTLS2:
+ cap = WHvCapabilityCodeVmxProcbasedCtls2;
+ break;
+ case MSR_IA32_VMX_EPT_VPID_CAP:
+ cap = WHvCapabilityCodeVmxEptVpidCap;
+ break;
+ case MSR_IA32_VMX_TRUE_PINBASED_CTLS:
+ cap = WHvCapabilityCodeVmxPinbasedCtls;
+ break;
+ case MSR_IA32_VMX_TRUE_PROCBASED_CTLS:
+ cap = WHvCapabilityCodeVmxProcbasedCtls;
+ break;
+ case MSR_IA32_VMX_TRUE_ENTRY_CTLS:
+ cap = WHvCapabilityCodeVmxTrueEntryCtls;
+ break;
+ case MSR_IA32_VMX_TRUE_EXIT_CTLS:
+ cap = WHvCapabilityCodeVmxTrueExitCtls;
+ break;
+ default:
+ cap = 0;
+ }
+
+ if (cap != 0) {
+ HRESULT hr = whp_dispatch.WHvGetCapability(
+ cap, &val, sizeof(val),
+ NULL);
+ if (FAILED(hr)) {
+ return 0;
+ }
+ return val;
+ }
+ return 0;
+}
+
/*
* Controls whether we should intercept various exceptions on the guest,
* namely breakpoint/single-step events.
@@ -2235,7 +2388,6 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
WHV_CAPABILITY_FEATURES features = {0};
WHV_PROCESSOR_FEATURES_BANKS processor_features;
WHV_PROCESSOR_PERFMON_FEATURES perfmon_features;
- bool is_legacy_os = false;
UINT32 cpuidExitList[] = {1};
whpx = &whpx_global;
@@ -2355,6 +2507,9 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
goto error;
}
+ whpx_rdtsc_cap = processor_features.Bank0.RdtscpSupport;
+ whpx_invpcid_cap = processor_features.Bank0.InvpcidSupport;
+
if (whpx_irqchip_in_kernel() && processor_features.Bank1.NestedVirtSupport) {
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
prop.NestedVirtualization = 1;
@@ -2395,7 +2550,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
if (FAILED(hr)) {
warn_report("WHPX: Failed to get performance "
"monitoring features, hr=%08lx", hr);
- is_legacy_os = true;
+ is_modern_os = false;
} else {
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
@@ -2435,7 +2590,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
synthetic_features.Bank0.DirectSyntheticTimers = 1;
}
- if (!is_legacy_os && whpx->hyperv_enlightenments_allowed) {
+ if (is_modern_os && whpx->hyperv_enlightenments_allowed) {
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks,
@@ -2446,7 +2601,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
ret = -EINVAL;
goto error;
}
- } else if (is_legacy_os && whpx->hyperv_enlightenments_required) {
+ } else if (!is_modern_os && whpx->hyperv_enlightenments_required) {
error_report("Hyper-V enlightenments not available on legacy Windows");
ret = -EINVAL;
goto error;
diff --git a/target/i386/whpx/whpx-cpu-legacy.c b/target/i386/whpx/whpx-cpu-legacy.c
new file mode 100644
index 00000000000..477429b460f
--- /dev/null
+++ b/target/i386/whpx/whpx-cpu-legacy.c
@@ -0,0 +1,171 @@
+/*
+ * i386 CPUID helper functions
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (c) 2017 Google Inc.
+ *
+ * This program 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.1 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * cpuid
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cpuid.h"
+#include "host/cpuinfo.h"
+#include "cpu.h"
+#include "emulate/x86.h"
+#include "whpx-i386.h"
+
+static bool cached_xcr0;
+static uint64_t supported_xcr0;
+
+static void cache_host_xcr0(void)
+{
+ if (cached_xcr0) {
+ return;
+ }
+
+ if (whpx_has_xsave()) {
+ uint64_t host_xcr0 = xgetbv_low(0);
+
+ /* Only show xcr0 bits corresponding to usable features. */
+ supported_xcr0 = host_xcr0 & (XSTATE_FP_MASK |
+ XSTATE_SSE_MASK | XSTATE_YMM_MASK |
+ XSTATE_OPMASK_MASK | XSTATE_ZMM_Hi256_MASK |
+ XSTATE_Hi16_ZMM_MASK);
+ if ((supported_xcr0 & (XSTATE_FP_MASK | XSTATE_SSE_MASK)) !=
+ (XSTATE_FP_MASK | XSTATE_SSE_MASK)) {
+ supported_xcr0 = 0;
+ }
+ }
+
+ cached_xcr0 = true;
+}
+
+uint32_t whpx_get_supported_cpuid_legacy(uint32_t func, uint32_t idx,
+ int reg)
+{
+ uint32_t eax, ebx, ecx, edx;
+
+ cache_host_xcr0();
+ host_cpuid(func, idx, &eax, &ebx, &ecx, &edx);
+
+ switch (func) {
+ case 0:
+ eax = eax < (uint32_t)0xd ? eax : (uint32_t)0xd;
+ break;
+ case 1:
+ edx &= CPUID_FP87 | CPUID_VME | CPUID_DE | CPUID_PSE | CPUID_TSC |
+ CPUID_MSR | CPUID_PAE | CPUID_MCE | CPUID_CX8 | CPUID_APIC |
+ CPUID_SEP | CPUID_MTRR | CPUID_PGE | CPUID_MCA | CPUID_CMOV |
+ CPUID_PAT | CPUID_PSE36 | CPUID_CLFLUSH | CPUID_MMX |
+ CPUID_FXSR | CPUID_SSE | CPUID_SSE2 | CPUID_SS | CPUID_HT;
+ ecx &= CPUID_EXT_SSE3 | CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSSE3 |
+ CPUID_EXT_FMA | CPUID_EXT_CX16 | CPUID_EXT_PCID |
+ CPUID_EXT_SSE41 | CPUID_EXT_SSE42 | CPUID_EXT_MOVBE |
+ CPUID_EXT_POPCNT | CPUID_EXT_AES |
+ (supported_xcr0 ? CPUID_EXT_XSAVE : 0) |
+ CPUID_EXT_AVX | CPUID_EXT_F16C | CPUID_EXT_RDRAND;
+ ecx |= CPUID_EXT_HYPERVISOR;
+ ecx |= CPUID_EXT_X2APIC;
+ edx |= CPUID_HT;
+ break;
+ case 6:
+ eax = CPUID_6_EAX_ARAT;
+ ebx = 0;
+ ecx = 0;
+ edx = 0;
+ break;
+ case 7:
+ if (idx == 0) {
+ ebx &= CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 |
+ CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 |
+ CPUID_7_0_EBX_SMEP | CPUID_7_0_EBX_BMI2 |
+ CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_RTM |
+ CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX |
+ CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_AVX512IFMA |
+ CPUID_7_0_EBX_AVX512F | CPUID_7_0_EBX_AVX512PF |
+ CPUID_7_0_EBX_AVX512ER | CPUID_7_0_EBX_AVX512CD |
+ CPUID_7_0_EBX_CLFLUSHOPT | CPUID_7_0_EBX_CLWB |
+ CPUID_7_0_EBX_AVX512DQ | CPUID_7_0_EBX_SHA_NI |
+ CPUID_7_0_EBX_AVX512BW | CPUID_7_0_EBX_AVX512VL |
+ CPUID_7_0_EBX_INVPCID;
+
+ if (!whpx_has_invpcid()) {
+ ebx &= ~CPUID_7_0_EBX_INVPCID;
+ }
+
+ ecx &= CPUID_7_0_ECX_AVX512_VBMI | CPUID_7_0_ECX_AVX512_VPOPCNTDQ |
+ CPUID_7_0_ECX_RDPID;
+ edx &= CPUID_7_0_EDX_AVX512_4VNNIW | CPUID_7_0_EDX_AVX512_4FMAPS;
+ } else {
+ ebx = 0;
+ ecx = 0;
+ edx = 0;
+ }
+ eax = 0;
+ break;
+ case 0xD:
+ if (!supported_xcr0 || idx >= 63 ||
+ (idx > 1 && !(supported_xcr0 & (UINT64_C(1) << idx)))) {
+ eax = ebx = ecx = edx = 0;
+ break;
+ }
+
+ if (idx == 0) {
+ eax = supported_xcr0;
+ } else if (idx == 1) {
+ eax &= CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XGETBV1;
+ if (!whpx_has_xsaves()) {
+ eax &= ~CPUID_XSAVE_XSAVES;
+ }
+ }
+ break;
+ case 0x80000001:
+ /* LM only if HVF in 64-bit mode */
+ edx &= CPUID_FP87 | CPUID_VME | CPUID_DE | CPUID_PSE | CPUID_TSC |
+ CPUID_MSR | CPUID_PAE | CPUID_MCE | CPUID_CX8 | CPUID_APIC |
+ CPUID_EXT2_SYSCALL | CPUID_MTRR | CPUID_PGE | CPUID_MCA | CPUID_CMOV |
+ CPUID_PAT | CPUID_PSE36 | CPUID_EXT2_MMXEXT | CPUID_MMX |
+ CPUID_FXSR | CPUID_EXT2_FXSR | CPUID_EXT2_PDPE1GB | CPUID_EXT2_3DNOWEXT |
+ CPUID_EXT2_3DNOW | CPUID_EXT2_LM | CPUID_EXT2_RDTSCP | CPUID_EXT2_NX;
+ if (!(whpx_has_rdtscp())) {
+ edx &= ~CPUID_EXT2_RDTSCP;
+ }
+ ecx &= CPUID_EXT3_LAHF_LM | CPUID_EXT3_CMP_LEG | CPUID_EXT3_CR8LEG |
+ CPUID_EXT3_ABM | CPUID_EXT3_SSE4A | CPUID_EXT3_MISALIGNSSE |
+ CPUID_EXT3_3DNOWPREFETCH | CPUID_EXT3_OSVW | CPUID_EXT3_XOP |
+ CPUID_EXT3_FMA4 | CPUID_EXT3_TBM;
+ break;
+ case 0x80000007:
+ edx &= CPUID_APM_INVTSC;
+ eax = ebx = ecx = 0;
+ break;
+ default:
+ return 0;
+ }
+
+ switch (reg) {
+ case R_EAX:
+ return eax;
+ case R_EBX:
+ return ebx;
+ case R_ECX:
+ return ecx;
+ case R_EDX:
+ return edx;
+ default:
+ return 0;
+ }
+}
diff --git a/target/i386/whpx/meson.build b/target/i386/whpx/meson.build
index c3aaaff9fd1..1c6a4ce3773 100644
--- a/target/i386/whpx/meson.build
+++ b/target/i386/whpx/meson.build
@@ -1,4 +1,5 @@
i386_system_ss.add(when: 'CONFIG_WHPX', if_true: files(
'whpx-all.c',
'whpx-apic.c',
+ 'whpx-cpu-legacy.c'
))
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 26/58] whpx: i386: disable TbFlushHypercalls for emulated LAPIC
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (23 preceding siblings ...)
2026-04-30 17:21 ` [PULL 25/58] whpx: i386: wire up feature probing Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 27/58] whpx: i386: enable x2apic by default for user-mode LAPIC Paolo Bonzini
` (32 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
AccessHypercallRegs was present twice so clean that up.
Remove TbFlushHypercalls (and its extended Gva range sub-feature)
from the user-mode LAPIC case as it behaves oddly there.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-5-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index d211b3f2efa..8f03616cb97 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2577,10 +2577,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
synthetic_features.Bank0.AccessPartitionReferenceTsc = 1;
synthetic_features.Bank0.AccessHypercallRegs = 1;
synthetic_features.Bank0.AccessFrequencyRegs = 1;
- synthetic_features.Bank0.EnableExtendedGvaRangesForFlushVirtualAddressList = 1;
synthetic_features.Bank0.AccessVpIndex = 1;
- synthetic_features.Bank0.AccessHypercallRegs = 1;
- synthetic_features.Bank0.TbFlushHypercalls = 1;
if (whpx_irqchip_in_kernel()) {
synthetic_features.Bank0.AccessSynicRegs = 1;
@@ -2588,6 +2585,12 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
synthetic_features.Bank0.AccessIntrCtrlRegs = 1;
synthetic_features.Bank0.SyntheticClusterIpi = 1;
synthetic_features.Bank0.DirectSyntheticTimers = 1;
+ /*
+ * These technically work without the Hyper-V LAPIC
+ * but behave oddly for multi-core VMs.
+ */
+ synthetic_features.Bank0.TbFlushHypercalls = 1;
+ synthetic_features.Bank0.EnableExtendedGvaRangesForFlushVirtualAddressList = 1;
}
if (is_modern_os && whpx->hyperv_enlightenments_allowed) {
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 27/58] whpx: i386: enable x2apic by default for user-mode LAPIC
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (24 preceding siblings ...)
2026-04-30 17:21 ` [PULL 26/58] whpx: i386: disable TbFlushHypercalls for emulated LAPIC Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 28/58] whpx: i386: reintroduce enlightenments for Windows 10 Paolo Bonzini
` (31 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-6-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 8f03616cb97..4954e0557a7 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2366,11 +2366,18 @@ error:
return ret;
}
+static PropValue whpx_default_props[] = {
+ { "x2apic", "on" },
+ { NULL, NULL },
+};
+
+
void whpx_cpu_instance_init(CPUState *cs)
{
X86CPU *cpu = X86_CPU(cs);
host_cpu_instance_init(cpu);
+ x86_cpu_apply_props(cpu, whpx_default_props);
}
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 28/58] whpx: i386: reintroduce enlightenments for Windows 10
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (25 preceding siblings ...)
2026-04-30 17:21 ` [PULL 27/58] whpx: i386: enable x2apic by default for user-mode LAPIC Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 29/58] whpx: i386: introduce proper cpuid support Paolo Bonzini
` (30 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Was removed in 2c08624 but it's still useful for
Windows 10 so reintroduce it there.
And this time, actually make it work by reporting
the hypervisor bit in CPUID.
Pretend to be vmware to be able to use vmport's functionality.
If the vmware frequency leaf is disabled, pretend to be
KVM, with the only capability reported being X2APIC support.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-7-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-internal.h | 1 +
accel/whpx/whpx-common.c | 2 ++
target/arm/whpx/whpx-all.c | 1 +
target/i386/whpx/whpx-all.c | 61 +++++++++++++++++++++++++++++-----
4 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h
index 5902124b637..cf782cf5f89 100644
--- a/include/system/whpx-internal.h
+++ b/include/system/whpx-internal.h
@@ -45,6 +45,7 @@ struct whpx_state {
bool hyperv_enlightenments_allowed;
bool hyperv_enlightenments_required;
+ bool hyperv_enlightenments_enabled;
};
diff --git a/accel/whpx/whpx-common.c b/accel/whpx/whpx-common.c
index b813a5d9d25..59be996aefc 100644
--- a/accel/whpx/whpx-common.c
+++ b/accel/whpx/whpx-common.c
@@ -550,6 +550,8 @@ static void whpx_accel_instance_init(Object *obj)
whpx->hyperv_enlightenments_allowed = true;
whpx->hyperv_enlightenments_required = false;
+ /* Value determined at whpx_accel_init */
+ whpx->hyperv_enlightenments_enabled = false;
}
static const TypeInfo whpx_accel_type = {
diff --git a/target/arm/whpx/whpx-all.c b/target/arm/whpx/whpx-all.c
index bbf0f6be961..4019a513aa7 100644
--- a/target/arm/whpx/whpx-all.c
+++ b/target/arm/whpx/whpx-all.c
@@ -968,6 +968,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
* as they're not needed for performance.
*/
if (whpx->hyperv_enlightenments_required) {
+ whpx->hyperv_enlightenments_enabled = true;
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks,
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 4954e0557a7..f9f330c038f 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2168,14 +2168,57 @@ int whpx_vcpu_run(CPUState *cpu)
vcpu->exit_ctx.VpContext.Rip +
vcpu->exit_ctx.VpContext.InstructionLength;
- reg_values[1].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRax;
- reg_values[2].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRcx;
- reg_values[3].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRdx;
- reg_values[4].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRbx;
+ if (whpx_is_legacy_os()) {
+ reg_values[1].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRax;
+ reg_values[2].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRcx;
+ reg_values[3].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRdx;
+ reg_values[4].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRbx;
+ } else {
+ cpu_x86_cpuid(env, vcpu->exit_ctx.CpuidAccess.Rax,
+ vcpu->exit_ctx.CpuidAccess.Rcx,
+ (UINT32 *)®_values[1].Reg32,
+ (UINT32 *)®_values[4].Reg32, (UINT32 *)®_values[2].Reg32,
+ (UINT32 *)®_values[3].Reg32);
+ }
- if (vcpu->exit_ctx.CpuidAccess.Rax == 1) {
- if (cpu_has_x2apic_feature(env)) {
- reg_values[2].Reg64 |= CPUID_EXT_X2APIC;
+ if (!whpx->hyperv_enlightenments_enabled) {
+ switch (vcpu->exit_ctx.CpuidAccess.Rax) {
+ case 1:
+ reg_values[2].Reg64 |= CPUID_EXT_HYPERVISOR;
+ break;
+ case 0x40000000:
+ /*
+ * Use vmware_cpuid_freq as a proxy to report VMware.
+ * This is to get the TSC/APIC frequency query functionality
+ * provided through vmport, as Linux doesn't use leaf
+ * 0x40000010 for getting those frequencies.
+ */
+ if (x86_cpu->vmware_cpuid_freq) {
+ reg_values[1].Reg64 = 0x40000010;
+ reg_values[4].Reg64 = 0x61774d56;
+ reg_values[2].Reg64 = 0x4d566572;
+ reg_values[3].Reg64 = 0x65726177;
+ } else {
+ /* report KVM otherwise if that's disabled */
+ reg_values[1].Reg64 = 0x40000001;
+ reg_values[4].Reg64 = 0x4b4d564b;
+ reg_values[2].Reg64 = 0x564b4d56;
+ reg_values[3].Reg64 = 0x4d;
+ }
+ break;
+ case 0x40000001:
+ if (!x86_cpu->vmware_cpuid_freq) {
+ /* KVM reporting of X2APIC support */
+ reg_values[1].Reg64 = reg_values[4].Reg64 =
+ reg_values[2].Reg64 = 1 << 15;
+ }
+ break;
+ case 0x40000010:
+ if (x86_cpu->vmware_cpuid_freq) {
+ reg_values[1].Reg64 = env->tsc_khz;
+ reg_values[4].Reg64 = env->apic_bus_freq / 1000; /* Hz to KHz */
+ }
+ break;
}
}
@@ -2396,6 +2439,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
WHV_PROCESSOR_FEATURES_BANKS processor_features;
WHV_PROCESSOR_PERFMON_FEATURES perfmon_features;
UINT32 cpuidExitList[] = {1};
+ UINT32 cpuidExitList_nohyperv[] = {1, 0x40000000, 0x40000001, 0x40000010};
whpx = &whpx_global;
@@ -2601,6 +2645,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
}
if (is_modern_os && whpx->hyperv_enlightenments_allowed) {
+ whpx->hyperv_enlightenments_enabled = true;
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks,
@@ -2653,7 +2698,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
WHvPartitionPropertyCodeCpuidExitList,
- cpuidExitList,
+ whpx->hyperv_enlightenments_enabled ? cpuidExitList : cpuidExitList_nohyperv,
RTL_NUMBER_OF(cpuidExitList) * sizeof(UINT32));
if (FAILED(hr)) {
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 29/58] whpx: i386: introduce proper cpuid support
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (26 preceding siblings ...)
2026-04-30 17:21 ` [PULL 28/58] whpx: i386: reintroduce enlightenments for Windows 10 Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 30/58] whpx: i386: kernel-irqchip=off fixes Paolo Bonzini
` (29 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Unlike the implementation in QEMU 10.2, this one works.
It's not optimal though as it doesn't use the Hyper-V support for this.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-8-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 136 +++++++++++++++++++++++++----
target/i386/whpx/whpx-cpu-legacy.c | 15 +---
2 files changed, 122 insertions(+), 29 deletions(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index f9f330c038f..73e351d895d 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2168,18 +2168,11 @@ int whpx_vcpu_run(CPUState *cpu)
vcpu->exit_ctx.VpContext.Rip +
vcpu->exit_ctx.VpContext.InstructionLength;
- if (whpx_is_legacy_os()) {
- reg_values[1].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRax;
- reg_values[2].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRcx;
- reg_values[3].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRdx;
- reg_values[4].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRbx;
- } else {
- cpu_x86_cpuid(env, vcpu->exit_ctx.CpuidAccess.Rax,
- vcpu->exit_ctx.CpuidAccess.Rcx,
- (UINT32 *)®_values[1].Reg32,
- (UINT32 *)®_values[4].Reg32, (UINT32 *)®_values[2].Reg32,
- (UINT32 *)®_values[3].Reg32);
- }
+ cpu_x86_cpuid(env, vcpu->exit_ctx.CpuidAccess.Rax,
+ vcpu->exit_ctx.CpuidAccess.Rcx,
+ (UINT32 *)®_values[1].Reg32,
+ (UINT32 *)®_values[4].Reg32, (UINT32 *)®_values[2].Reg32,
+ (UINT32 *)®_values[3].Reg32);
if (!whpx->hyperv_enlightenments_enabled) {
switch (vcpu->exit_ctx.CpuidAccess.Rax) {
@@ -2220,6 +2213,68 @@ int whpx_vcpu_run(CPUState *cpu)
}
break;
}
+ } else {
+ switch (vcpu->exit_ctx.CpuidAccess.Rax) {
+ case 0x40000000:
+ case 0x40000001:
+ case 0x40000010:
+ reg_values[1].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRax;
+ reg_values[2].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRcx;
+ reg_values[3].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRdx;
+ reg_values[4].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRbx;
+ break;
+ }
+ }
+
+ if (vcpu->exit_ctx.CpuidAccess.Rax == 0x1) {
+ if (cpu_has_x2apic_feature(env)) {
+ reg_values[2].Reg64 |= CPUID_EXT_X2APIC;
+ } else {
+ reg_values[2].Reg32 &= ~CPUID_EXT_X2APIC;
+ }
+ }
+
+ /* Dynamic depending on XCR0 and XSS, so query DefaultResult */
+ if (vcpu->exit_ctx.CpuidAccess.Rax == 0x07
+ && vcpu->exit_ctx.CpuidAccess.Rcx == 0) {
+ if (vcpu->exit_ctx.CpuidAccess.DefaultResultRdx
+ & CPUID_7_0_EDX_CET_IBT) {
+ reg_values[3].Reg32 |= CPUID_7_0_EDX_CET_IBT;
+ } else {
+ reg_values[3].Reg32 &= ~CPUID_7_0_EDX_CET_IBT;
+ }
+
+ if (vcpu->exit_ctx.CpuidAccess.DefaultResultRcx
+ & CPUID_7_0_ECX_CET_SHSTK) {
+ reg_values[2].Reg32 |= CPUID_7_0_ECX_CET_SHSTK;
+ } else {
+ reg_values[2].Reg32 &= ~CPUID_7_0_ECX_CET_SHSTK;
+ }
+
+ if (vcpu->exit_ctx.CpuidAccess.DefaultResultRcx
+ & CPUID_7_0_ECX_OSPKE) {
+ reg_values[2].Reg32 |= CPUID_7_0_ECX_OSPKE;
+ } else {
+ reg_values[2].Reg32 &= ~CPUID_7_0_ECX_OSPKE;
+ }
+ }
+
+ /* CPUID[0xD,{1,2}].EBX are dynamic depending on guest features. */
+ if (vcpu->exit_ctx.CpuidAccess.Rax == 0xd) {
+ if (vcpu->exit_ctx.CpuidAccess.Rcx == 1
+ || vcpu->exit_ctx.CpuidAccess.Rcx == 2) {
+ reg_values[4].Reg64 = vcpu->exit_ctx.CpuidAccess.DefaultResultRbx;
+ }
+ }
+
+ /* OSXSAVE is dynamic. Do this instead of syncing CR4 */
+ if (vcpu->exit_ctx.CpuidAccess.Rax == 1) {
+ if (vcpu->exit_ctx.CpuidAccess.DefaultResultRcx
+ & CPUID_EXT_OSXSAVE) {
+ reg_values[2].Reg32 |= CPUID_EXT_OSXSAVE;
+ } else {
+ reg_values[2].Reg32 &= ~CPUID_EXT_OSXSAVE;
+ }
}
hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
@@ -2409,6 +2464,45 @@ error:
return ret;
}
+static void whpx_cpu_xsave_init(void)
+{
+ static bool first = true;
+ int i;
+
+ if (!first) {
+ return;
+ }
+ first = false;
+
+ /* x87 and SSE states are in the legacy region of the XSAVE area. */
+ x86_ext_save_areas[XSTATE_FP_BIT].offset = 0;
+ x86_ext_save_areas[XSTATE_SSE_BIT].offset = 0;
+
+ for (i = XSTATE_SSE_BIT + 1; i < XSAVE_STATE_AREA_COUNT; i++) {
+ ExtSaveArea *esa = &x86_ext_save_areas[i];
+
+ if (esa->size) {
+ int sz = whpx_get_supported_cpuid(0xd, i, R_EAX);
+ if (sz != 0) {
+ assert(esa->size == sz);
+ esa->offset = whpx_get_supported_cpuid(0xd, i, R_EBX);
+ }
+ }
+ }
+}
+
+static void whpx_cpu_max_instance_init(X86CPU *cpu)
+{
+ CPUX86State *env = &cpu->env;
+
+ env->cpuid_min_level =
+ whpx_get_supported_cpuid(0x0, 0, R_EAX);
+ env->cpuid_min_xlevel =
+ whpx_get_supported_cpuid(0x80000000, 0, R_EAX);
+ env->cpuid_min_xlevel2 =
+ whpx_get_supported_cpuid(0xC0000000, 0, R_EAX);
+}
+
static PropValue whpx_default_props[] = {
{ "x2apic", "on" },
{ NULL, NULL },
@@ -2418,9 +2512,18 @@ static PropValue whpx_default_props[] = {
void whpx_cpu_instance_init(CPUState *cs)
{
X86CPU *cpu = X86_CPU(cs);
+ X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu);
host_cpu_instance_init(cpu);
x86_cpu_apply_props(cpu, whpx_default_props);
+
+ if (xcc->max_features) {
+ whpx_cpu_max_instance_init(cpu);
+ }
+
+ if (whpx_has_xsave()) {
+ whpx_cpu_xsave_init();
+ }
}
/*
@@ -2438,8 +2541,11 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
WHV_CAPABILITY_FEATURES features = {0};
WHV_PROCESSOR_FEATURES_BANKS processor_features;
WHV_PROCESSOR_PERFMON_FEATURES perfmon_features;
- UINT32 cpuidExitList[] = {1};
- UINT32 cpuidExitList_nohyperv[] = {1, 0x40000000, 0x40000001, 0x40000010};
+
+ UINT32 cpuidExitList[] = {0x0, 0x1, 0x6, 0x7, 0xb, 0xd, 0x14, 0x24, 0x29, 0x1E,
+ 0x40000000, 0x40000001, 0x40000010, 0x80000000, 0x80000001,
+ 0x80000002, 0x80000003, 0x80000004, 0x80000007, 0x80000008,
+ 0x8000000A, 0x80000021, 0x80000022, 0xC0000000, 0xC0000001};
whpx = &whpx_global;
@@ -2698,7 +2804,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
WHvPartitionPropertyCodeCpuidExitList,
- whpx->hyperv_enlightenments_enabled ? cpuidExitList : cpuidExitList_nohyperv,
+ cpuidExitList,
RTL_NUMBER_OF(cpuidExitList) * sizeof(UINT32));
if (FAILED(hr)) {
diff --git a/target/i386/whpx/whpx-cpu-legacy.c b/target/i386/whpx/whpx-cpu-legacy.c
index 477429b460f..d341e6f4fd1 100644
--- a/target/i386/whpx/whpx-cpu-legacy.c
+++ b/target/i386/whpx/whpx-cpu-legacy.c
@@ -4,20 +4,7 @@
* Copyright (c) 2003 Fabrice Bellard
* Copyright (c) 2017 Google Inc.
*
- * This program 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.1 of the License, or (at your option) any later version.
- *
- * This program 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 program; if not, see <http://www.gnu.org/licenses/>.
- *
- * cpuid
+ * SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "qemu/osdep.h"
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 30/58] whpx: i386: kernel-irqchip=off fixes
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (27 preceding siblings ...)
2026-04-30 17:21 ` [PULL 29/58] whpx: i386: introduce proper cpuid support Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 31/58] whpx: i386: use WHvX64RegisterCr8 only when kernel-irqchip=off Paolo Bonzini
` (28 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
This was really... quite broken. After fixing this,
Windows boots with kernel-irqchip=off.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-9-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-common.h | 1 +
target/i386/whpx/whpx-all.c | 43 +++++-------------------------------
2 files changed, 7 insertions(+), 37 deletions(-)
diff --git a/include/system/whpx-common.h b/include/system/whpx-common.h
index 04289afd973..3406c20fec0 100644
--- a/include/system/whpx-common.h
+++ b/include/system/whpx-common.h
@@ -4,6 +4,7 @@
struct AccelCPUState {
bool window_registered;
+ int window_priority;
bool interruptable;
bool ready_for_pic_interrupt;
uint64_t tpr;
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 73e351d895d..d470c5b9d37 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -22,6 +22,8 @@
#include "qemu/main-loop.h"
#include "hw/core/boards.h"
#include "hw/intc/ioapic.h"
+#include "hw/intc/i8259.h"
+#include "hw/i386/x86.h"
#include "hw/i386/apic_internal.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
@@ -390,28 +392,6 @@ static int whpx_set_tsc(CPUState *cpu)
return 0;
}
-/*
- * The CR8 register in the CPU is mapped to the TPR register of the APIC,
- * however, they use a slightly different encoding. Specifically:
- *
- * APIC.TPR[bits 7:4] = CR8[bits 3:0]
- *
- * This mechanism is described in section 10.8.6.1 of Volume 3 of Intel 64
- * and IA-32 Architectures Software Developer's Manual.
- *
- * The functions below translate the value of CR8 to TPR and vice versa.
- */
-
-static uint64_t whpx_apic_tpr_to_cr8(uint64_t tpr)
-{
- return tpr >> 4;
-}
-
-static uint64_t whpx_cr8_to_apic_tpr(uint64_t cr8)
-{
- return cr8 << 4;
-}
-
void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
{
struct whpx_state *whpx = &whpx_global;
@@ -440,7 +420,7 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
v86 = (env->eflags & VM_MASK);
r86 = !(env->cr[0] & CR0_PE_MASK);
- vcpu->tpr = whpx_apic_tpr_to_cr8(cpu_get_apic_tpr(x86_cpu->apic_state));
+ vcpu->tpr = cpu_get_apic_tpr(x86_cpu->apic_state);
vcpu->apic_base = cpu_get_apic_base(x86_cpu->apic_state);
idx = 0;
@@ -711,17 +691,6 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
hr);
}
- if (whpx_irqchip_in_kernel()) {
- /*
- * Fetch the TPR value from the emulated APIC. It may get overwritten
- * below with the value from CR8 returned by
- * WHvGetVirtualProcessorRegisters().
- */
- whpx_apic_get(x86_cpu->apic_state);
- vcpu->tpr = whpx_apic_tpr_to_cr8(
- cpu_get_apic_tpr(x86_cpu->apic_state));
- }
-
idx = 0;
/* Indexes for first 16 registers match between HV and QEMU definitions */
@@ -770,7 +739,7 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
tpr = vcxt.values[idx++].Reg64;
if (tpr != vcpu->tpr) {
vcpu->tpr = tpr;
- cpu_set_apic_tpr(x86_cpu->apic_state, whpx_cr8_to_apic_tpr(tpr));
+ cpu_set_apic_tpr(x86_cpu->apic_state, tpr);
}
/* 8 Debug Registers - Skipped */
@@ -1775,7 +1744,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu)
}
/* Sync the TPR to the CR8 if was modified during the intercept */
- tpr = whpx_apic_tpr_to_cr8(cpu_get_apic_tpr(x86_cpu->apic_state));
+ tpr = cpu_get_apic_tpr(x86_cpu->apic_state);
if (tpr != vcpu->tpr) {
vcpu->tpr = tpr;
reg_values[reg_count].Reg64 = tpr;
@@ -1822,7 +1791,7 @@ static void whpx_vcpu_post_run(CPUState *cpu)
if (vcpu->tpr != tpr) {
vcpu->tpr = tpr;
bql_lock();
- cpu_set_apic_tpr(x86_cpu->apic_state, whpx_cr8_to_apic_tpr(vcpu->tpr));
+ cpu_set_apic_tpr(x86_cpu->apic_state, vcpu->tpr);
bql_unlock();
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 31/58] whpx: i386: use WHvX64RegisterCr8 only when kernel-irqchip=off
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (28 preceding siblings ...)
2026-04-30 17:21 ` [PULL 30/58] whpx: i386: kernel-irqchip=off fixes Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 32/58] whpx: i386: disable kernel-irqchip on Windows 10 when PIC enabled Paolo Bonzini
` (27 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
When kernel-irqchip=on, manage TPR as part of the APIC state instead entirely.
This fixes some failure to set state errors.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-10-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 37 ++++++++++++++++++++++---------------
1 file changed, 22 insertions(+), 15 deletions(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index d470c5b9d37..03c146dfb8c 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -95,7 +95,6 @@ static const WHV_REGISTER_NAME whpx_register_names[] = {
WHvX64RegisterCr2,
WHvX64RegisterCr3,
WHvX64RegisterCr4,
- WHvX64RegisterCr8,
/* X64 Debug Registers */
/*
@@ -478,8 +477,11 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
vcxt.values[idx++].Reg64 = env->cr[3];
assert(whpx_register_names[idx] == WHvX64RegisterCr4);
vcxt.values[idx++].Reg64 = env->cr[4];
- assert(whpx_register_names[idx] == WHvX64RegisterCr8);
- vcxt.values[idx++].Reg64 = vcpu->tpr;
+ /* For kernel-irqchip=on, TPR is managed as part of APIC state */
+ if (!whpx_irqchip_in_kernel()) {
+ WHV_REGISTER_VALUE cr8 = {.Reg64 = vcpu->tpr};
+ whpx_set_reg(cpu, WHvX64RegisterCr8, cr8);
+ }
/* 8 Debug Registers - Skipped */
@@ -735,11 +737,14 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
env->cr[3] = vcxt.values[idx++].Reg64;
assert(whpx_register_names[idx] == WHvX64RegisterCr4);
env->cr[4] = vcxt.values[idx++].Reg64;
- assert(whpx_register_names[idx] == WHvX64RegisterCr8);
- tpr = vcxt.values[idx++].Reg64;
- if (tpr != vcpu->tpr) {
- vcpu->tpr = tpr;
- cpu_set_apic_tpr(x86_cpu->apic_state, tpr);
+
+ /* For kernel-irqchip=on, TPR is managed as part of APIC state */
+ if (!whpx_irqchip_in_kernel()) {
+ tpr = vcpu->exit_ctx.VpContext.Cr8;
+ if (tpr != vcpu->tpr) {
+ vcpu->tpr = tpr;
+ cpu_set_apic_tpr(x86_cpu->apic_state, tpr);
+ }
}
/* 8 Debug Registers - Skipped */
@@ -1745,7 +1750,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu)
/* Sync the TPR to the CR8 if was modified during the intercept */
tpr = cpu_get_apic_tpr(x86_cpu->apic_state);
- if (tpr != vcpu->tpr) {
+ if (!whpx_irqchip_in_kernel() && tpr != vcpu->tpr) {
vcpu->tpr = tpr;
reg_values[reg_count].Reg64 = tpr;
qatomic_set(&cpu->exit_request, true);
@@ -1787,12 +1792,14 @@ static void whpx_vcpu_post_run(CPUState *cpu)
env->eflags = vcpu->exit_ctx.VpContext.Rflags;
- uint64_t tpr = vcpu->exit_ctx.VpContext.Cr8;
- if (vcpu->tpr != tpr) {
- vcpu->tpr = tpr;
- bql_lock();
- cpu_set_apic_tpr(x86_cpu->apic_state, vcpu->tpr);
- bql_unlock();
+ if (!whpx_irqchip_in_kernel()) {
+ uint64_t tpr = vcpu->exit_ctx.VpContext.Cr8;
+ if (vcpu->tpr != tpr) {
+ vcpu->tpr = tpr;
+ bql_lock();
+ cpu_set_apic_tpr(x86_cpu->apic_state, vcpu->tpr);
+ bql_unlock();
+ }
}
vcpu->interruption_pending =
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 32/58] whpx: i386: disable kernel-irqchip on Windows 10 when PIC enabled
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (29 preceding siblings ...)
2026-04-30 17:21 ` [PULL 31/58] whpx: i386: use WHvX64RegisterCr8 only when kernel-irqchip=off Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 33/58] whpx: i386: IO port fast path cleanup Paolo Bonzini
` (26 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Move WHvCapabilityCodeProcessorPerfmonFeatures queries
as that's how we distinguish if on a legacy OS.
Now that Windows guests are booting, disable kernel-irqchip=on
by default for Windows 10 when the PIC is enabled.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-11-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 69 +++++++++++++++++++++----------------
1 file changed, 39 insertions(+), 30 deletions(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 03c146dfb8c..ab8e97787fe 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2523,6 +2523,13 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
0x80000002, 0x80000003, 0x80000004, 0x80000007, 0x80000008,
0x8000000A, 0x80000021, 0x80000022, 0xC0000000, 0xC0000001};
+ X86MachineState *x86ms = X86_MACHINE(ms);
+ bool pic_enabled = false;
+
+ if (x86ms->pic == ON_OFF_AUTO_ON || x86ms->pic == ON_OFF_AUTO_AUTO) {
+ pic_enabled = true;
+ }
+
whpx = &whpx_global;
if (!init_whp_dispatch()) {
@@ -2594,6 +2601,35 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
goto error;
}
+ /* Enable supported performance monitoring capabilities */
+ hr = whp_dispatch.WHvGetCapability(
+ WHvCapabilityCodeProcessorPerfmonFeatures, &perfmon_features,
+ sizeof(WHV_PROCESSOR_PERFMON_FEATURES), &whpx_cap_size);
+ /*
+ * Relying on this is a crutch to maintain Windows 10 support.
+ *
+ * WHvCapabilityCodeProcessorPerfmonFeatures and
+ * WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks
+ * are implemented starting from Windows Server 2022 (build 20348).
+ */
+ if (FAILED(hr)) {
+ warn_report("WHPX: Failed to get performance "
+ "monitoring features, hr=%08lx", hr);
+ is_modern_os = false;
+ } else {
+ hr = whp_dispatch.WHvSetPartitionProperty(
+ whpx->partition,
+ WHvPartitionPropertyCodeProcessorPerfmonFeatures,
+ &perfmon_features,
+ sizeof(WHV_PROCESSOR_PERFMON_FEATURES));
+ if (FAILED(hr)) {
+ error_report("WHPX: Failed to set performance "
+ "monitoring features, hr=%08lx", hr);
+ ret = -EINVAL;
+ goto error;
+ }
+ }
+
/*
* Error out if WHP doesn't support apic emulation and user is requiring
* it.
@@ -2606,8 +2642,9 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
goto error;
}
- if (whpx->kernel_irqchip_allowed && features.LocalApicEmulation &&
- whp_dispatch.WHvSetVirtualProcessorInterruptControllerState2) {
+ if (whpx->kernel_irqchip_allowed && !(whpx_is_legacy_os() && pic_enabled
+ && !whpx->kernel_irqchip_required) && features.LocalApicEmulation
+ && whp_dispatch.WHvSetVirtualProcessorInterruptControllerState2) {
WHV_X64_LOCAL_APIC_EMULATION_MODE mode =
WHvX64LocalApicEmulationModeX2Apic;
hr = whp_dispatch.WHvSetPartitionProperty(
@@ -2669,34 +2706,6 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
goto error;
}
- /* Enable supported performance monitoring capabilities */
- hr = whp_dispatch.WHvGetCapability(
- WHvCapabilityCodeProcessorPerfmonFeatures, &perfmon_features,
- sizeof(WHV_PROCESSOR_PERFMON_FEATURES), &whpx_cap_size);
- /*
- * Relying on this is a crutch to maintain Windows 10 support.
- *
- * WHvCapabilityCodeProcessorPerfmonFeatures and
- * WHvPartitionPropertyCodeSyntheticProcessorFeaturesBanks
- * are implemented starting from Windows Server 2022 (build 20348).
- */
- if (FAILED(hr)) {
- warn_report("WHPX: Failed to get performance "
- "monitoring features, hr=%08lx", hr);
- is_modern_os = false;
- } else {
- hr = whp_dispatch.WHvSetPartitionProperty(
- whpx->partition,
- WHvPartitionPropertyCodeProcessorPerfmonFeatures,
- &perfmon_features,
- sizeof(WHV_PROCESSOR_PERFMON_FEATURES));
- if (FAILED(hr)) {
- error_report("WHPX: Failed to set performance "
- "monitoring features, hr=%08lx", hr);
- ret = -EINVAL;
- goto error;
- }
- }
/* Enable synthetic processor features */
WHV_SYNTHETIC_PROCESSOR_FEATURES_BANKS synthetic_features;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 33/58] whpx: i386: IO port fast path cleanup
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (30 preceding siblings ...)
2026-04-30 17:21 ` [PULL 32/58] whpx: i386: disable kernel-irqchip on Windows 10 when PIC enabled Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 34/58] whpx: i386: disable enlightenments and LAPIC for isapc Paolo Bonzini
` (25 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
vmport calls synchronise_state within an I/O port read.
Support that properly.
What was there before worked because of a side effect of
whpx_get_reg synchronising context if cpu->vcpu_dirty.
Remove that whpx_get_reg call in whpx_bump_rip too as it's no longer
needed now.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-12-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index ab8e97787fe..75f0f062d44 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -881,7 +881,6 @@ static void handle_io(CPUState *env, uint16_t port, void *buffer,
static void whpx_bump_rip(CPUState *cpu, WHV_RUN_VP_EXIT_CONTEXT *exit_ctx)
{
WHV_REGISTER_VALUE reg;
- whpx_get_reg(cpu, WHvX64RegisterRip, ®);
reg.Reg64 = exit_ctx->VpContext.Rip + exit_ctx->VpContext.InstructionLength;
whpx_set_reg(cpu, WHvX64RegisterRip, reg);
}
@@ -909,13 +908,23 @@ static int whpx_handle_portio(CPUState *cpu,
} else {
reg.Reg64 = (uint64_t)val;
}
- whpx_bump_rip(cpu, exit_ctx);
- whpx_set_reg(cpu, WHvX64RegisterRax, reg);
+ /* vmport calls cpu_synchronize_state on an I/O port read */
+ if (!cpu->vcpu_dirty) {
+ whpx_bump_rip(cpu, exit_ctx);
+ whpx_set_reg(cpu, WHvX64RegisterRax, reg);
+ } else {
+ env->eip = exit_ctx->VpContext.Rip + exit_ctx->VpContext.InstructionLength;
+ env->regs[R_EAX] = reg.Reg64;
+ }
return 0;
} else if (!ctx->AccessInfo.StringOp && ctx->AccessInfo.IsWrite) {
RAX(env) = ctx->Rax;
handle_io(cpu, ctx->PortNumber, &RAX(env), 1, ctx->AccessInfo.AccessSize, 1);
- whpx_bump_rip(cpu, exit_ctx);
+ if (!cpu->vcpu_dirty) {
+ whpx_bump_rip(cpu, exit_ctx);
+ } else {
+ env->eip = exit_ctx->VpContext.Rip + exit_ctx->VpContext.InstructionLength;
+ }
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 34/58] whpx: i386: disable enlightenments and LAPIC for isapc
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (31 preceding siblings ...)
2026-04-30 17:21 ` [PULL 33/58] whpx: i386: IO port fast path cleanup Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 35/58] whpx: i386: interrupt priority support Paolo Bonzini
` (24 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
The isapc machine doesn't have an APIC. And Hyper-V enlightenments
don't sound too useful to have there so disable those.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-13-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 75f0f062d44..cbcf1de7ae4 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2546,6 +2546,14 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
goto error;
}
+ /* for isapc, disable Hyper-V enlightenments and LAPIC */
+ if (!strcmp(MACHINE_GET_CLASS(ms)->name, "isapc")) {
+ whpx->kernel_irqchip_allowed = false;
+ whpx->kernel_irqchip_required = false;
+ whpx->hyperv_enlightenments_allowed = false;
+ whpx->hyperv_enlightenments_required = false;
+ }
+
whpx->mem_quota = ms->ram_size;
hr = whp_dispatch.WHvGetCapability(
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 35/58] whpx: i386: interrupt priority support
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (32 preceding siblings ...)
2026-04-30 17:21 ` [PULL 34/58] whpx: i386: disable enlightenments and LAPIC for isapc Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 36/58] hw/intc: apic: disallow APIC reads when disabled Paolo Bonzini
` (23 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Implement APIC IRR interrupt priorities.
Even with kernel-irqchip=off, Hyper-V is aware of interrupt priorities
and implements CR8/TPR, with the InterruptPriority field being followed.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-14-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index cbcf1de7ae4..012fa6d0216 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -1673,6 +1673,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu)
UINT32 reg_count = 0;
WHV_REGISTER_VALUE reg_values[3];
WHV_REGISTER_NAME reg_names[3];
+ int irr = apic_get_highest_priority_irr(x86_cpu->apic_state);
memset(&new_int, 0, sizeof(new_int));
memset(reg_values, 0, sizeof(reg_values));
@@ -1708,10 +1709,20 @@ static void whpx_vcpu_pre_run(CPUState *cpu)
}
}
+ if (irr == -1) {
+ if (isa_pic != NULL && pic_get_output(isa_pic)) {
+ /* In case it's a PIC interrupt */
+ irr = 0;
+ } else if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD)) {
+ abort();
+ }
+ }
+
/* Get pending hard interruption or replay one that was overwritten */
if (!whpx_irqchip_in_kernel()) {
if (!vcpu->interruption_pending &&
- vcpu->interruptable && (env->eflags & IF_MASK)) {
+ vcpu->interruptable && (env->eflags & IF_MASK)
+ && (vcpu->tpr < irr || irr == 0)) {
assert(!new_int.InterruptionPending);
if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD)) {
cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD);
@@ -1768,13 +1779,17 @@ static void whpx_vcpu_pre_run(CPUState *cpu)
}
/* Update the state of the interrupt delivery notification */
- if (!vcpu->window_registered &&
+ if ((!vcpu->window_registered ||
+ (vcpu->window_priority < irr && vcpu->window_priority != 0) ||
+ (irr == 0 && vcpu->window_priority != 0)) &&
cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD)) {
reg_values[reg_count].DeliverabilityNotifications =
(WHV_X64_DELIVERABILITY_NOTIFICATIONS_REGISTER) {
- .InterruptNotification = 1
+ .InterruptNotification = 1,
+ .InterruptPriority = irr >> 4
};
vcpu->window_registered = 1;
+ vcpu->window_priority = irr;
reg_names[reg_count] = WHvX64RegisterDeliverabilityNotifications;
reg_count += 1;
}
@@ -1788,7 +1803,7 @@ static void whpx_vcpu_pre_run(CPUState *cpu)
reg_names, reg_count, reg_values);
if (FAILED(hr)) {
error_report("WHPX: Failed to set interrupt state registers,"
- " hr=%08lx", hr);
+ " hr=%08lx, InterruptPriority=%i", hr, irr >> 4);
}
}
}
@@ -2004,6 +2019,7 @@ int whpx_vcpu_run(CPUState *cpu)
case WHvRunVpExitReasonX64InterruptWindow:
vcpu->ready_for_pic_interrupt = 1;
vcpu->window_registered = 0;
+ vcpu->window_priority = 0;
ret = 0;
break;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 36/58] hw/intc: apic: disallow APIC reads when disabled
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (33 preceding siblings ...)
2026-04-30 17:21 ` [PULL 35/58] whpx: i386: interrupt priority support Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 37/58] whpx: i386: fix CPUID[1:EDX].APIC reporting Paolo Bonzini
` (22 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
!APICBASE_ENABLE + attempting to read xAPIC registers is not an allowed combination.
And neither is x2APIC enabled + attempting to read xAPIC registers
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-15-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
hw/intc/apic.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/hw/intc/apic.c b/hw/intc/apic.c
index 8766ed00b92..e5ea8312617 100644
--- a/hw/intc/apic.c
+++ b/hw/intc/apic.c
@@ -875,6 +875,15 @@ static uint64_t apic_mem_read(void *opaque, hwaddr addr, unsigned size)
return -1;
}
+ /* if the xAPIC is disabled, return early. */
+ if (!(s->apicbase & MSR_IA32_APICBASE_ENABLE)) {
+ return 0xffffffff;
+ }
+
+ if (is_x2apic_mode(s)) {
+ return 0xffffffff;
+ }
+
index = (addr >> 4) & 0xff;
apic_register_read(s, index, &val);
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 37/58] whpx: i386: fix CPUID[1:EDX].APIC reporting
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (34 preceding siblings ...)
2026-04-30 17:21 ` [PULL 36/58] hw/intc: apic: disallow APIC reads when disabled Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 38/58] whpx: i386: set apicbase value only on success Paolo Bonzini
` (21 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Hyper-V always has CPUID[1:EDX].APIC set, even when the APIC isn't enabled yet.
Work around this by also using the APICBASE trap for kernel-irqchip=on.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-16-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-common.h | 1 -
target/i386/whpx/whpx-all.c | 34 ++++++++---------
target/i386/whpx/whpx-apic.c | 71 ++++++++++++++++++++++++++++++++++--
3 files changed, 84 insertions(+), 22 deletions(-)
diff --git a/include/system/whpx-common.h b/include/system/whpx-common.h
index 3406c20fec0..79710e2fb3c 100644
--- a/include/system/whpx-common.h
+++ b/include/system/whpx-common.h
@@ -8,7 +8,6 @@ struct AccelCPUState {
bool interruptable;
bool ready_for_pic_interrupt;
uint64_t tpr;
- uint64_t apic_base;
bool interruption_pending;
/* Must be the last field as it may have a tail */
WHV_RUN_VP_EXIT_CONTEXT exit_ctx;
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 012fa6d0216..b0556445801 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -139,7 +139,6 @@ static const WHV_REGISTER_NAME whpx_register_names[] = {
#ifdef TARGET_X86_64
WHvX64RegisterKernelGsBase,
#endif
- WHvX64RegisterApicBase,
/* WHvX64RegisterPat, */
WHvX64RegisterSysenterCs,
WHvX64RegisterSysenterEip,
@@ -420,7 +419,6 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
r86 = !(env->cr[0] & CR0_PE_MASK);
vcpu->tpr = cpu_get_apic_tpr(x86_cpu->apic_state);
- vcpu->apic_base = cpu_get_apic_base(x86_cpu->apic_state);
idx = 0;
@@ -538,9 +536,6 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
vcxt.values[idx++].Reg64 = env->kernelgsbase;
#endif
- assert(whpx_register_names[idx] == WHvX64RegisterApicBase);
- vcxt.values[idx++].Reg64 = vcpu->apic_base;
-
/* WHvX64RegisterPat - Skipped */
assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs);
@@ -575,6 +570,12 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
error_report("WHPX: Failed to set virtual processor context, hr=%08lx",
hr);
}
+
+ if (level >= WHPX_LEVEL_FULL_STATE) {
+ WHV_REGISTER_VALUE apic_base = {};
+ apic_base.Reg64 = cpu_get_apic_base(X86_CPU(cpu)->apic_state);
+ whpx_set_reg(cpu, WHvX64RegisterApicBase, apic_base);
+ }
}
static int whpx_get_tsc(CPUState *cpu)
@@ -666,7 +667,7 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
X86CPU *x86_cpu = X86_CPU(cpu);
CPUX86State *env = &x86_cpu->env;
struct whpx_register_set vcxt;
- uint64_t tpr, apic_base;
+ uint64_t tpr;
HRESULT hr;
int idx;
int idx_next;
@@ -798,13 +799,6 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
env->kernelgsbase = vcxt.values[idx++].Reg64;
#endif
- assert(whpx_register_names[idx] == WHvX64RegisterApicBase);
- apic_base = vcxt.values[idx++].Reg64;
- if (apic_base != vcpu->apic_base) {
- vcpu->apic_base = apic_base;
- cpu_set_apic_base(x86_cpu->apic_state, vcpu->apic_base);
- }
-
/* WHvX64RegisterPat - Skipped */
assert(whpx_register_names[idx] == WHvX64RegisterSysenterCs);
@@ -2082,8 +2076,7 @@ int whpx_vcpu_run(CPUState *cpu)
val = X86_CPU(cpu)->env.apic_bus_freq;
}
- if (!whpx_irqchip_in_kernel() &&
- vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
+ if (vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
is_known_msr = 1;
if (!vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
/* Read path unreachable on Hyper-V */
@@ -2233,6 +2226,13 @@ int whpx_vcpu_run(CPUState *cpu)
} else {
reg_values[2].Reg32 &= ~CPUID_EXT_X2APIC;
}
+
+ /* CPUID[1:EDX].APIC is dynamic */
+ if (env->features[FEAT_1_EDX] & CPUID_APIC) {
+ reg_values[3].Reg32 |= CPUID_APIC;
+ } else {
+ reg_values[3].Reg32 &= ~CPUID_APIC;
+ }
}
/* Dynamic depending on XCR0 and XSS, so query DefaultResult */
@@ -2804,9 +2804,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
prop.X64MsrExitBitmap.UnhandledMsrs = 1;
- if (!whpx_irqchip_in_kernel()) {
- prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
- }
+ prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
diff --git a/target/i386/whpx/whpx-apic.c b/target/i386/whpx/whpx-apic.c
index f26ecaf6e83..65629ca45f9 100644
--- a/target/i386/whpx/whpx-apic.c
+++ b/target/i386/whpx/whpx-apic.c
@@ -90,9 +90,70 @@ static void whpx_get_apic_state(APICCommonState *s,
apic_next_timer(s, s->initial_count_load_time);
}
-static int whpx_apic_set_base(APICCommonState *s, uint64_t val)
+static int apic_set_base_check(APICCommonState *s, uint64_t val)
{
- s->apicbase = val;
+ /* Enable x2apic when x2apic is not supported by CPU */
+ if (!cpu_has_x2apic_feature(&s->cpu->env) &&
+ val & MSR_IA32_APICBASE_EXTD) {
+ return -1;
+ }
+
+ /*
+ * Transition into invalid state
+ * (s->apicbase & MSR_IA32_APICBASE_ENABLE == 0) &&
+ * (s->apicbase & MSR_IA32_APICBASE_EXTD) == 1
+ */
+ if (!(val & MSR_IA32_APICBASE_ENABLE) &&
+ (val & MSR_IA32_APICBASE_EXTD)) {
+ return -1;
+ }
+
+ /* Invalid transition from disabled mode to x2APIC */
+ if (!(s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
+ !(s->apicbase & MSR_IA32_APICBASE_EXTD) &&
+ (val & MSR_IA32_APICBASE_ENABLE) &&
+ (val & MSR_IA32_APICBASE_EXTD)) {
+ return -1;
+ }
+
+ /* Invalid transition from x2APIC to xAPIC */
+ if ((s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
+ (s->apicbase & MSR_IA32_APICBASE_EXTD) &&
+ (val & MSR_IA32_APICBASE_ENABLE) &&
+ !(val & MSR_IA32_APICBASE_EXTD)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int apic_set_base(APICCommonState *s, uint64_t val)
+{
+ if (apic_set_base_check(s, val) < 0) {
+ return -1;
+ }
+
+ s->apicbase = (val & MSR_IA32_APICBASE_BASE) |
+ (s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE));
+ if (!(val & MSR_IA32_APICBASE_ENABLE)) {
+ s->apicbase &= ~MSR_IA32_APICBASE_ENABLE;
+ cpu_clear_apic_feature(&s->cpu->env);
+ }
+
+ /* Transition from disabled mode to xAPIC */
+ if (!(s->apicbase & MSR_IA32_APICBASE_ENABLE) &&
+ (val & MSR_IA32_APICBASE_ENABLE)) {
+ s->apicbase |= MSR_IA32_APICBASE_ENABLE;
+ cpu_set_apic_feature(&s->cpu->env);
+ }
+
+ /* Transition from xAPIC to x2APIC */
+ if (cpu_has_x2apic_feature(&s->cpu->env) &&
+ !(s->apicbase & MSR_IA32_APICBASE_EXTD) &&
+ (val & MSR_IA32_APICBASE_EXTD)) {
+ s->apicbase |= MSR_IA32_APICBASE_EXTD;
+ }
+
return 0;
}
@@ -235,6 +296,10 @@ static void whpx_apic_mem_write(void *opaque, hwaddr addr,
static const MemoryRegionOps whpx_apic_io_ops = {
.read = whpx_apic_mem_read,
.write = whpx_apic_mem_write,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
.endianness = DEVICE_LITTLE_ENDIAN,
};
@@ -262,7 +327,7 @@ static void whpx_apic_class_init(ObjectClass *klass, const void *data)
k->realize = whpx_apic_realize;
k->reset = whpx_apic_reset;
- k->set_base = whpx_apic_set_base;
+ k->set_base = apic_set_base;
k->set_tpr = whpx_apic_set_tpr;
k->get_tpr = whpx_apic_get_tpr;
k->post_load = whpx_apic_post_load;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 38/58] whpx: i386: set apicbase value only on success
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (35 preceding siblings ...)
2026-04-30 17:21 ` [PULL 37/58] whpx: i386: fix CPUID[1:EDX].APIC reporting Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 39/58] whpx: i386: enable GuestIdleReg enlightenment Paolo Bonzini
` (20 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-17-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index b0556445801..3426811c482 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2086,8 +2086,9 @@ int whpx_vcpu_run(CPUState *cpu)
int msr_ret = cpu_set_apic_base(X86_CPU(cpu)->apic_state, val);
if (msr_ret < 0) {
x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ } else {
+ whpx_set_reg(cpu, WHvX64RegisterApicBase, reg);
}
- whpx_set_reg(cpu, WHvX64RegisterApicBase, reg);
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 39/58] whpx: i386: enable GuestIdleReg enlightenment
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (36 preceding siblings ...)
2026-04-30 17:21 ` [PULL 38/58] whpx: i386: set apicbase value only on success Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 40/58] whpx: i386: unknown MSR configurability Paolo Bonzini
` (19 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
This corresponds to HV_X64_MSR_GUEST_IDLE (0x400000f0).
This enlightenment is only available by the HV when using the Hyper-V LAPIC.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-19-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 3426811c482..9fc44f8a67e 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2761,6 +2761,7 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
synthetic_features.Bank0.AccessIntrCtrlRegs = 1;
synthetic_features.Bank0.SyntheticClusterIpi = 1;
synthetic_features.Bank0.DirectSyntheticTimers = 1;
+ synthetic_features.Bank0.AccessGuestIdleReg = 1;
/*
* These technically work without the Hyper-V LAPIC
* but behave oddly for multi-core VMs.
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 40/58] whpx: i386: unknown MSR configurability
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (37 preceding siblings ...)
2026-04-30 17:21 ` [PULL 39/58] whpx: i386: enable GuestIdleReg enlightenment Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 41/58] whpx: i386: don't increment eip on MSR access raising GPF Paolo Bonzini
` (18 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Add an option to inject back a GPF for unknown MSRs.
Keep it on by default for now as Linux expects accesses to some
AMD-specific MSRs to always succeed when on an AMD host.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-18-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-all.h | 1 +
include/system/whpx-internal.h | 1 +
accel/whpx/whpx-common.c | 3 +++
target/arm/whpx/whpx-all.c | 4 +++
target/i386/whpx/whpx-all.c | 45 ++++++++++++++++++++++++++++++++++
5 files changed, 54 insertions(+)
diff --git a/include/system/whpx-all.h b/include/system/whpx-all.h
index 2cbea71b149..4022571ffff 100644
--- a/include/system/whpx-all.h
+++ b/include/system/whpx-all.h
@@ -20,6 +20,7 @@ void whpx_translate_cpu_breakpoints(
CPUState *cpu,
int cpu_breakpoint_count);
void whpx_arch_destroy_vcpu(CPUState *cpu);
+void whpx_arch_accel_class_init(ObjectClass *oc);
/* called by whpx-accel-ops */
bool whpx_arch_supports_guest_debug(void);
diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h
index cf782cf5f89..86639627b36 100644
--- a/include/system/whpx-internal.h
+++ b/include/system/whpx-internal.h
@@ -47,6 +47,7 @@ struct whpx_state {
bool hyperv_enlightenments_required;
bool hyperv_enlightenments_enabled;
+ bool ignore_unknown_msr;
};
extern struct whpx_state whpx_global;
diff --git a/accel/whpx/whpx-common.c b/accel/whpx/whpx-common.c
index 59be996aefc..497c03138ec 100644
--- a/accel/whpx/whpx-common.c
+++ b/accel/whpx/whpx-common.c
@@ -538,6 +538,8 @@ static void whpx_accel_class_init(ObjectClass *oc, const void *data)
NULL, NULL);
object_class_property_set_description(oc, "hyperv",
"Configure Hyper-V enlightenments");
+
+ whpx_arch_accel_class_init(oc);
}
static void whpx_accel_instance_init(Object *obj)
@@ -552,6 +554,7 @@ static void whpx_accel_instance_init(Object *obj)
whpx->hyperv_enlightenments_required = false;
/* Value determined at whpx_accel_init */
whpx->hyperv_enlightenments_enabled = false;
+ whpx->ignore_unknown_msr = true;
}
static const TypeInfo whpx_accel_type = {
diff --git a/target/arm/whpx/whpx-all.c b/target/arm/whpx/whpx-all.c
index 4019a513aa7..94304a4230b 100644
--- a/target/arm/whpx/whpx-all.c
+++ b/target/arm/whpx/whpx-all.c
@@ -823,6 +823,10 @@ void whpx_cpu_instance_init(CPUState *cs)
{
}
+void whpx_arch_accel_class_init(ObjectClass *oc)
+{
+}
+
int whpx_accel_init(AccelState *as, MachineState *ms)
{
struct whpx_state *whpx;
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 9fc44f8a67e..eecc7f48ed9 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2133,6 +2133,10 @@ int whpx_vcpu_run(CPUState *cpu)
vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite);
}
+ if (!is_known_msr && !whpx->ignore_unknown_msr) {
+ x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ }
+
hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
whpx->partition,
cpu->cpu_index,
@@ -2532,6 +2536,47 @@ void whpx_cpu_instance_init(CPUState *cs)
* Partition support
*/
+static void whpx_set_unknown_msr(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ struct whpx_state *whpx = &whpx_global;
+ OnOffAuto mode;
+
+ if (!visit_type_OnOffAuto(v, name, &mode, errp)) {
+ return;
+ }
+
+ switch (mode) {
+ case ON_OFF_AUTO_ON:
+ whpx->ignore_unknown_msr = true;
+ break;
+
+ case ON_OFF_AUTO_OFF:
+ whpx->ignore_unknown_msr = false;
+ break;
+
+ case ON_OFF_AUTO_AUTO:
+ whpx->ignore_unknown_msr = true;
+ break;
+ default:
+ /*
+ * The value was checked in visit_type_OnOffAuto() above. If
+ * we get here, then something is wrong in QEMU.
+ */
+ abort();
+ }
+}
+
+void whpx_arch_accel_class_init(ObjectClass *oc)
+{
+ object_class_property_add(oc, "ignore-unknown-msr", "OnOffAuto",
+ NULL, whpx_set_unknown_msr,
+ NULL, NULL);
+ object_class_property_set_description(oc, "ignore-unknown-msr",
+ "Configure unknown MSR behavior");
+}
+
int whpx_accel_init(AccelState *as, MachineState *ms)
{
struct whpx_state *whpx;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 41/58] whpx: i386: don't increment eip on MSR access raising GPF
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (38 preceding siblings ...)
2026-04-30 17:21 ` [PULL 40/58] whpx: i386: unknown MSR configurability Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 42/58] target/i386: emulate, hvf: rdmsr/wrmsr GPF handling Paolo Bonzini
` (17 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-33-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index eecc7f48ed9..4bb99a8e900 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2048,6 +2048,7 @@ int whpx_vcpu_run(CPUState *cpu)
WHV_REGISTER_NAME reg_names[3];
UINT32 reg_count;
bool is_known_msr = 0;
+ bool raises_gpf = false;
uint64_t val;
if (vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
@@ -2086,6 +2087,7 @@ int whpx_vcpu_run(CPUState *cpu)
int msr_ret = cpu_set_apic_base(X86_CPU(cpu)->apic_state, val);
if (msr_ret < 0) {
x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ raises_gpf = true;
} else {
whpx_set_reg(cpu, WHvX64RegisterApicBase, reg);
}
@@ -2105,6 +2107,7 @@ int whpx_vcpu_run(CPUState *cpu)
reg_values[1].Reg64 = val;
if (msr_ret < 0) {
x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ raises_gpf = true;
}
} else {
bql_lock();
@@ -2112,6 +2115,7 @@ int whpx_vcpu_run(CPUState *cpu)
bql_unlock();
if (msr_ret < 0) {
x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ raises_gpf = true;
}
}
}
@@ -2135,6 +2139,13 @@ int whpx_vcpu_run(CPUState *cpu)
if (!is_known_msr && !whpx->ignore_unknown_msr) {
x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ raises_gpf = true;
+ }
+
+ /* When a GPF is raised, do not change Rip. */
+ if (raises_gpf) {
+ reg_values[0].Reg64 =
+ vcpu->exit_ctx.VpContext.Rip;
}
hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 42/58] target/i386: emulate, hvf: rdmsr/wrmsr GPF handling
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (39 preceding siblings ...)
2026-04-30 17:21 ` [PULL 41/58] whpx: i386: don't increment eip on MSR access raising GPF Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 43/58] whpx: i386: tighten APIC base validity check Paolo Bonzini
` (16 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
In that case, the instruction pointer mustn't be incremented.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-34-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/emulate/x86_emu.h | 4 ++--
target/i386/hvf/hvf-i386.h | 4 ++--
target/i386/emulate/x86_emu.c | 10 ++++++----
target/i386/hvf/hvf.c | 9 +++++++--
4 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/target/i386/emulate/x86_emu.h b/target/i386/emulate/x86_emu.h
index a8d4c93098d..b985240b90e 100644
--- a/target/i386/emulate/x86_emu.h
+++ b/target/i386/emulate/x86_emu.h
@@ -31,8 +31,8 @@ struct x86_emul_ops {
target_ulong (*read_cr) (CPUState *cpu, int cr);
void (*handle_io)(CPUState *cpu, uint16_t port, void *data, int direction,
int size, int count);
- void (*simulate_rdmsr)(CPUState *cs);
- void (*simulate_wrmsr)(CPUState *cs);
+ bool (*simulate_rdmsr)(CPUState *cs);
+ bool (*simulate_wrmsr)(CPUState *cs);
bool (*is_protected_mode)(CPUState *cpu);
bool (*is_long_mode)(CPUState *cpu);
bool (*is_user_mode)(CPUState *cpu);
diff --git a/target/i386/hvf/hvf-i386.h b/target/i386/hvf/hvf-i386.h
index 8c42ae6b013..b91c17e2fc2 100644
--- a/target/i386/hvf/hvf-i386.h
+++ b/target/i386/hvf/hvf-i386.h
@@ -19,8 +19,8 @@
uint32_t hvf_get_supported_cpuid(uint32_t func, uint32_t idx, int reg);
void hvf_handle_io(CPUState *, uint16_t, void *, int, int, int);
-void hvf_simulate_rdmsr(CPUState *cpu);
-void hvf_simulate_wrmsr(CPUState *cpu);
+bool hvf_simulate_rdmsr(CPUState *cpu);
+bool hvf_simulate_wrmsr(CPUState *cpu);
/* Host specific functions */
int hvf_inject_interrupt(CPUArchState *env, int vector);
diff --git a/target/i386/emulate/x86_emu.c b/target/i386/emulate/x86_emu.c
index c2da1a133f3..c6ea8542902 100644
--- a/target/i386/emulate/x86_emu.c
+++ b/target/i386/emulate/x86_emu.c
@@ -792,15 +792,17 @@ void x86_emul_raise_exception(CPUX86State *env, int exception_index, int error_c
static bool exec_rdmsr(CPUX86State *env, struct x86_decode *decode)
{
- emul_ops->simulate_rdmsr(env_cpu(env));
- env->eip += decode->len;
+ if (!emul_ops->simulate_rdmsr(env_cpu(env))) {
+ env->eip += decode->len;
+ }
return 0;
}
static bool exec_wrmsr(CPUX86State *env, struct x86_decode *decode)
{
- emul_ops->simulate_wrmsr(env_cpu(env));
- env->eip += decode->len;
+ if (!emul_ops->simulate_wrmsr(env_cpu(env))) {
+ env->eip += decode->len;
+ }
return 0;
}
diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c
index cdc8bd19504..2d1a943b96c 100644
--- a/target/i386/hvf/hvf.c
+++ b/target/i386/hvf/hvf.c
@@ -536,7 +536,7 @@ void hvf_store_regs(CPUState *cs)
macvm_set_rip(cs, env->eip);
}
-void hvf_simulate_rdmsr(CPUState *cs)
+bool hvf_simulate_rdmsr(CPUState *cs)
{
X86CPU *cpu = X86_CPU(cs);
CPUX86State *env = &cpu->env;
@@ -557,6 +557,7 @@ void hvf_simulate_rdmsr(CPUState *cs)
ret = apic_msr_read(cpu->apic_state, index, &val);
if (ret < 0) {
x86_emul_raise_exception(env, EXCP0D_GPF, 0);
+ return 1;
}
break;
@@ -639,9 +640,10 @@ void hvf_simulate_rdmsr(CPUState *cs)
RAX(env) = (uint32_t)val;
RDX(env) = (uint32_t)(val >> 32);
+ return 0;
}
-void hvf_simulate_wrmsr(CPUState *cs)
+bool hvf_simulate_wrmsr(CPUState *cs)
{
X86CPU *cpu = X86_CPU(cs);
CPUX86State *env = &cpu->env;
@@ -657,6 +659,7 @@ void hvf_simulate_wrmsr(CPUState *cs)
r = cpu_set_apic_base(cpu->apic_state, data);
if (r < 0) {
x86_emul_raise_exception(env, EXCP0D_GPF, 0);
+ return 1;
}
break;
@@ -668,6 +671,7 @@ void hvf_simulate_wrmsr(CPUState *cs)
ret = apic_msr_write(cpu->apic_state, index, data);
if (ret < 0) {
x86_emul_raise_exception(env, EXCP0D_GPF, 0);
+ return 1;
}
break;
@@ -746,6 +750,7 @@ void hvf_simulate_wrmsr(CPUState *cs)
g_hypervisor_iface->wrmsr_handler(cs, msr, data);
printf("write msr %llx\n", RCX(cs));*/
+ return 0;
}
static int hvf_handle_vmexit(CPUState *cpu)
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 43/58] whpx: i386: tighten APIC base validity check
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (40 preceding siblings ...)
2026-04-30 17:21 ` [PULL 42/58] target/i386: emulate, hvf: rdmsr/wrmsr GPF handling Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 44/58] whpx: i386: ignore vpassist when kernel-irqchip=off Paolo Bonzini
` (15 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-20-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 4bb99a8e900..8fbce415903 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2079,6 +2079,10 @@ int whpx_vcpu_run(CPUState *cpu)
if (vcpu->exit_ctx.MsrAccess.MsrNumber == MSR_IA32_APICBASE) {
is_known_msr = 1;
+ if (val & MSR_IA32_APICBASE_RESERVED) {
+ x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ raises_gpf = true;
+ }
if (!vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite) {
/* Read path unreachable on Hyper-V */
abort();
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 44/58] whpx: i386: ignore vpassist when kernel-irqchip=off
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (41 preceding siblings ...)
2026-04-30 17:21 ` [PULL 43/58] whpx: i386: tighten APIC base validity check Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 45/58] target: i386: HLT type that ignores EFLAGS.IF Paolo Bonzini
` (14 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Linux tries to set vpassist even when none of the enlightenments
using it are available.
So ignore the page it sets.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-21-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 8fbce415903..11c9d8729fa 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -51,6 +51,7 @@
#define HYPERV_APIC_BUS_FREQUENCY (200000000ULL)
/* for kernel-irqchip=off */
#define HV_X64_MSR_APIC_FREQUENCY 0x40000023
+#define HV_X64_MSR_VP_ASSIST_PAGE 0x40000073
static bool is_modern_os = true;
@@ -2123,6 +2124,18 @@ int whpx_vcpu_run(CPUState *cpu)
}
}
}
+
+ /*
+ * Linux tries to use it anyway even when not exposed.
+ * Ignore the write as the VP assist page is not used.
+ */
+ if (vcpu->exit_ctx.MsrAccess.MsrNumber == HV_X64_MSR_VP_ASSIST_PAGE
+ && vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite
+ && !whpx_irqchip_in_kernel()
+ && whpx->hyperv_enlightenments_enabled) {
+ is_known_msr = 1;
+ }
+
/*
* For all unsupported MSR access we:
* ignore writes
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 45/58] target: i386: HLT type that ignores EFLAGS.IF
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (42 preceding siblings ...)
2026-04-30 17:21 ` [PULL 44/58] whpx: i386: ignore vpassist when kernel-irqchip=off Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 46/58] whpx: i386: add HV_X64_MSR_GUEST_IDLE when !kernel-irqchip Paolo Bonzini
` (13 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
The TLFS says:
> A partition which possesses the AccessGuestIdleMsr privilege may trigger
> entry into the virtual processor idle sleep state through a read to the
> hypervisor-defined MSR HV_X64_MSR_GUEST_IDLE. The virtual processor will
> be woken when an interrupt arrives, regardless of whether the interrupt
> is enabled on the virtual processor or not.
Meanwhile, Windows 24H2+ calls this MSR anyway without the privilege being set.
Add the infrastructure to support it on the generic QEMU side.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-22-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/cpu.h | 9 +++++++++
target/i386/cpu.c | 10 ++++------
target/i386/hvf/x86hvf.c | 4 ++--
target/i386/whpx/whpx-all.c | 5 ++++-
4 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 6401028e70d..c14237967b5 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -225,6 +225,7 @@ typedef enum X86Seg {
#define HF2_NPT_SHIFT 6 /* Nested Paging enabled */
#define HF2_IGNNE_SHIFT 7 /* Ignore CR0.NE=0 */
#define HF2_VGIF_SHIFT 8 /* Can take VIRQ*/
+#define HF2_HYPERV_HLT_SHIFT 9 /* Hyper-V HV_X64_MSR_GUEST_IDLE */
#define HF2_GIF_MASK (1 << HF2_GIF_SHIFT)
#define HF2_HIF_MASK (1 << HF2_HIF_SHIFT)
@@ -235,6 +236,7 @@ typedef enum X86Seg {
#define HF2_NPT_MASK (1 << HF2_NPT_SHIFT)
#define HF2_IGNNE_MASK (1 << HF2_IGNNE_SHIFT)
#define HF2_VGIF_MASK (1 << HF2_VGIF_SHIFT)
+#define HF2_HYPERV_HLT_MASK (1 << HF2_HYPERV_HLT_SHIFT)
#define CR0_PE_SHIFT 0
#define CR0_MP_SHIFT 1
@@ -3085,6 +3087,13 @@ static inline bool ctl_has_irq(CPUX86State *env)
return (env->int_ctl & V_IRQ_MASK) && (int_prio >= tpr);
}
+static inline bool x86_cpu_interrupts_enabled(CPUX86State *env)
+{
+ return ((env->eflags & IF_MASK) &&
+ !(env->hflags & HF_INHIBIT_IRQ_MASK)) ||
+ (env->hflags2 & HF2_HYPERV_HLT_MASK);
+}
+
#if defined(TARGET_X86_64) && \
defined(CONFIG_USER_ONLY) && \
defined(CONFIG_LINUX)
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index 7ea80f07c7c..efe7ba014d3 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -10617,14 +10617,12 @@ int x86_cpu_pending_interrupt(CPUState *cs, int interrupt_request)
(((env->hflags2 & HF2_VINTR_MASK) &&
(env->hflags2 & HF2_HIF_MASK)) ||
(!(env->hflags2 & HF2_VINTR_MASK) &&
- (env->eflags & IF_MASK &&
- !(env->hflags & HF_INHIBIT_IRQ_MASK))))) {
+ x86_cpu_interrupts_enabled(env)))) {
return CPU_INTERRUPT_HARD;
} else if (env->hflags2 & HF2_VGIF_MASK) {
- if((interrupt_request & CPU_INTERRUPT_VIRQ) &&
- (env->eflags & IF_MASK) &&
- !(env->hflags & HF_INHIBIT_IRQ_MASK)) {
- return CPU_INTERRUPT_VIRQ;
+ if ((interrupt_request & CPU_INTERRUPT_VIRQ) &&
+ x86_cpu_interrupts_enabled(env)) {
+ return CPU_INTERRUPT_VIRQ;
}
}
}
diff --git a/target/i386/hvf/x86hvf.c b/target/i386/hvf/x86hvf.c
index bb480311b0f..16b810f3c2e 100644
--- a/target/i386/hvf/x86hvf.c
+++ b/target/i386/hvf/x86hvf.c
@@ -405,9 +405,9 @@ bool hvf_inject_interrupts(CPUState *cs)
}
}
- if (!(env->hflags & HF_INHIBIT_IRQ_MASK) &&
+ if (x86_cpu_interrupts_enabled(env) &&
cpu_test_interrupt(cs, CPU_INTERRUPT_HARD) &&
- (env->eflags & IF_MASK) && !(info & VMCS_INTR_VALID)) {
+ !(info & VMCS_INTR_VALID)) {
int line = cpu_get_pic_interrupt(env);
cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
if (line >= 0) {
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 11c9d8729fa..bc8d673c31a 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -1630,11 +1630,14 @@ static vaddr whpx_vcpu_get_pc(CPUState *cpu, bool exit_context_valid)
static int whpx_handle_halt(CPUState *cpu)
{
+ X86CPU *x86_cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86_cpu->env;
+
int ret = 0;
bql_lock();
if (!(cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD) &&
- (cpu_env(cpu)->eflags & IF_MASK)) &&
+ x86_cpu_interrupts_enabled(env)) &&
!cpu_test_interrupt(cpu, CPU_INTERRUPT_NMI)) {
cpu->exception_index = EXCP_HLT;
cpu->halted = true;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 46/58] whpx: i386: add HV_X64_MSR_GUEST_IDLE when !kernel-irqchip
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (43 preceding siblings ...)
2026-04-30 17:21 ` [PULL 45/58] target: i386: HLT type that ignores EFLAGS.IF Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 47/58] whpx: i386: some x2APIC awareness Paolo Bonzini
` (12 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Add support for an oddball HV_X64_MSR_GUEST_IDLE not-quite-an-HLT
that wakes the vCPU even if EFLAGS.IF is set.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-23-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index bc8d673c31a..593a707e219 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -52,6 +52,7 @@
/* for kernel-irqchip=off */
#define HV_X64_MSR_APIC_FREQUENCY 0x40000023
#define HV_X64_MSR_VP_ASSIST_PAGE 0x40000073
+#define HV_X64_MSR_GUEST_IDLE 0x400000f0
static bool is_modern_os = true;
@@ -1648,6 +1649,15 @@ static int whpx_handle_halt(CPUState *cpu)
return ret;
}
+static int whpx_handle_hyperv_guestidle(CPUState *cpu)
+{
+ X86CPU *x86_cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86_cpu->env;
+
+ env->hflags2 |= HF2_HYPERV_HLT_MASK;
+ return whpx_handle_halt(cpu);
+}
+
static void whpx_vcpu_kick_out_of_hlt(CPUState *cpu)
{
WHV_REGISTER_VALUE reg;
@@ -1851,9 +1861,10 @@ static void whpx_vcpu_process_async_events(CPUState *cpu)
}
if ((cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD) &&
- (env->eflags & IF_MASK)) ||
+ ((env->eflags & IF_MASK) || (env->hflags2 & HF2_HYPERV_HLT_MASK))) ||
cpu_test_interrupt(cpu, CPU_INTERRUPT_NMI)) {
cpu->halted = false;
+ env->hflags2 &= ~HF2_HYPERV_HLT_MASK;
}
if (cpu_test_interrupt(cpu, CPU_INTERRUPT_SIPI)) {
@@ -2128,6 +2139,20 @@ int whpx_vcpu_run(CPUState *cpu)
}
}
+ /*
+ * Windows and Linux both use this MSR.
+ * Windows 11 25H2 uses it even when not advertised.
+ */
+ if (vcpu->exit_ctx.MsrAccess.MsrNumber == HV_X64_MSR_GUEST_IDLE
+ && !vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite
+ && !whpx_irqchip_in_kernel()
+ && whpx->hyperv_enlightenments_enabled) {
+ is_known_msr = 1;
+ whpx_bump_rip(cpu, &vcpu->exit_ctx);
+ ret = whpx_handle_hyperv_guestidle(cpu);
+ break;
+ }
+
/*
* Linux tries to use it anyway even when not exposed.
* Ignore the write as the VP assist page is not used.
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 47/58] whpx: i386: some x2APIC awareness
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (44 preceding siblings ...)
2026-04-30 17:21 ` [PULL 46/58] whpx: i386: add HV_X64_MSR_GUEST_IDLE when !kernel-irqchip Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 48/58] whpx: i386: set WHvX64RegisterInitialApicId Paolo Bonzini
` (11 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-24-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-apic.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/target/i386/whpx/whpx-apic.c b/target/i386/whpx/whpx-apic.c
index 65629ca45f9..cc272f82a5a 100644
--- a/target/i386/whpx/whpx-apic.c
+++ b/target/i386/whpx/whpx-apic.c
@@ -33,7 +33,11 @@ static void whpx_put_apic_state(APICCommonState *s,
int i;
memset(kapic, 0, sizeof(*kapic));
- kapic->fields[0x2].data = s->id << 24;
+ if (s->apicbase & MSR_IA32_APICBASE_EXTD) {
+ kapic->fields[0x2].data = s->initial_apic_id;
+ } else {
+ kapic->fields[0x2].data = s->id << 24;
+ }
kapic->fields[0x3].data = s->version | ((APIC_LVT_NB - 1) << 16);
kapic->fields[0x8].data = s->tpr;
kapic->fields[0xd].data = s->log_dest << 24;
@@ -61,7 +65,11 @@ static void whpx_get_apic_state(APICCommonState *s,
{
int i, v;
- s->id = kapic->fields[0x2].data >> 24;
+ if (s->apicbase & MSR_IA32_APICBASE_EXTD) {
+ assert(kapic->fields[0x2].data == s->initial_apic_id);
+ } else {
+ s->id = kapic->fields[0x2].data >> 24;
+ }
s->tpr = kapic->fields[0x8].data;
s->arb_id = kapic->fields[0x9].data;
s->log_dest = kapic->fields[0xd].data >> 24;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 48/58] whpx: i386: set WHvX64RegisterInitialApicId
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (45 preceding siblings ...)
2026-04-30 17:21 ` [PULL 47/58] whpx: i386: some x2APIC awareness Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 49/58] whpx: i386: Pause VM on fatal exception to be able to inspect state Paolo Bonzini
` (10 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Keep Hyper-V aware of the initial APIC ID chosen.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-25-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 593a707e219..077a491664d 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2475,6 +2475,11 @@ int whpx_init_vcpu(CPUState *cpu)
goto error;
}
+ if (!whpx_irqchip_in_kernel()) {
+ WHV_REGISTER_VALUE apic_id = {.Reg64 = x86_cpu->apic_state->initial_apic_id};
+ whpx_set_reg(cpu, WHvX64RegisterInitialApicId, apic_id);
+ }
+
/*
* vcpu's TSC frequency is either specified by user, or use the value
* provided by Hyper-V if the former is not present. In the latter case, we
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 49/58] whpx: i386: Pause VM on fatal exception to be able to inspect state
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (46 preceding siblings ...)
2026-04-30 17:21 ` [PULL 48/58] whpx: i386: set WHvX64RegisterInitialApicId Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 50/58] target/i386: emulate: use exception_payload for fault address Paolo Bonzini
` (9 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-26-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 077a491664d..25ace38ff60 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2387,7 +2387,7 @@ int whpx_vcpu_run(CPUState *cpu)
vcpu->exit_ctx.ExitReason);
whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE);
bql_lock();
- qemu_system_guest_panicked(cpu_get_crash_info(cpu));
+ vm_stop(RUN_STATE_PAUSED);
bql_unlock();
break;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 50/58] target/i386: emulate: use exception_payload for fault address
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (47 preceding siblings ...)
2026-04-30 17:21 ` [PULL 49/58] whpx: i386: Pause VM on fatal exception to be able to inspect state Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 51/58] target/i386: make xsave_buf present unconditionally Paolo Bonzini
` (8 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Instead of directly putting it in cr[2], put it in exception_payload.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-27-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/emulate/x86_mmu.c | 3 ++-
target/i386/whpx/whpx-all.c | 6 +++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/target/i386/emulate/x86_mmu.c b/target/i386/emulate/x86_mmu.c
index 007de582de9..8d4371467fd 100644
--- a/target/i386/emulate/x86_mmu.c
+++ b/target/i386/emulate/x86_mmu.c
@@ -277,7 +277,8 @@ static MMUTranslateResult x86_write_mem_ex(CPUState *cpu, void *data, target_ulo
translate_res = mmu_gva_to_gpa(cpu, gva, &gpa, translate_flags);
if (translate_res) {
int error_code = translate_res_to_error_code(translate_res, true, is_user(cpu));
- env->cr[2] = gva;
+ env->exception_has_payload = 1;
+ env->exception_payload = gva;
x86_emul_raise_exception(env, EXCP0E_PAGE, error_code);
return translate_res;
}
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 25ace38ff60..8d8aabf4db7 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -1891,11 +1891,11 @@ static void whpx_inject_exceptions(CPUState* cpu)
WHV_REGISTER_VALUE reg = {};
reg.ExceptionEvent.EventPending = 1;
reg.ExceptionEvent.EventType = WHvX64PendingEventException;
- reg.ExceptionEvent.DeliverErrorCode = 1;
+ reg.ExceptionEvent.DeliverErrorCode = env->has_error_code;
reg.ExceptionEvent.Vector = env->exception_nr;
reg.ExceptionEvent.ErrorCode = env->error_code;
- if (env->exception_nr == EXCP0E_PAGE) {
- reg.ExceptionEvent.ExceptionParameter = env->cr[2];
+ if (env->exception_has_payload) {
+ reg.ExceptionEvent.ExceptionParameter = env->exception_payload;
}
whpx_set_reg(cpu, WHvRegisterPendingEvent, reg);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 51/58] target/i386: make xsave_buf present unconditionally
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (48 preceding siblings ...)
2026-04-30 17:21 ` [PULL 50/58] target/i386: emulate: use exception_payload for fault address Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 52/58] target/i386: add de/compaction to xsave_helper Paolo Bonzini
` (7 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
This is necessary for the xsave_helper helpers to compile.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-28-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/cpu.h | 2 --
1 file changed, 2 deletions(-)
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index c14237967b5..2a6c54dc293 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -2270,10 +2270,8 @@ typedef struct CPUArchState {
int64_t user_tsc_khz; /* for sanity check only */
uint64_t apic_bus_freq;
uint64_t tsc;
-#if defined(CONFIG_KVM) || defined(CONFIG_HVF)
void *xsave_buf;
uint32_t xsave_buf_len;
-#endif
#if defined(CONFIG_KVM)
struct kvm_nested_state *nested_state;
MemoryRegion *xen_vcpu_info_mr;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 52/58] target/i386: add de/compaction to xsave_helper
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (49 preceding siblings ...)
2026-04-30 17:21 ` [PULL 51/58] target/i386: make xsave_buf present unconditionally Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 53/58] whpx: xsave support Paolo Bonzini
` (6 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Magnus Kulke, Mohamed Mediouni
From: Magnus Kulke <magnuskulke@linux.microsoft.com>
HyperV use XSAVES which stores extended state in compacted format in
which components are packed contiguously, while QEMU's internal XSAVE
representation use the standard format in which each component is places
at a fixed offset. Hence for this purpose we add two conversion fn's to
the xsave helper to roundtrip XSAVE state in a migration.
- decompact_xsave_area(): converts compacted format to standard.
XSTATE_BV is masked to host XCR0 since IA32_XSS is managed
by the hypervisor.
- compact_xsave_area(): converts standard format back to compacted
format. XCOMP_BV is set from the host's CPUID 0xD.0 rather than the
guest's XCR0, as this is what the hypervisor expects.
Both functions use the host's CPUID leaf 0xD subleaves to determine component
sizes, offsets, and alignment requirements.
There are situations when the host advertises features that we want to
disable for the guest, e.g. AMX TILE. In this case we cannot rely on the
host's xcr0, but instead we use the feature mask that has been generated
in as part of the CPU realization process (x86_cpu_expand_features).
Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com>
[Fixup: made xsave_offset a size_t to fix macOS and OpenBSD builds]
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-29-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/cpu.h | 2 +
target/i386/xsave_helper.c | 256 +++++++++++++++++++++++++++++++++++++
target/i386/meson.build | 5 +-
3 files changed, 260 insertions(+), 3 deletions(-)
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 2a6c54dc293..ba4b1972acc 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -3024,6 +3024,8 @@ void x86_cpu_xrstor_all_areas(X86CPU *cpu, const void *buf, uint32_t buflen);
void x86_cpu_xsave_all_areas(X86CPU *cpu, void *buf, uint32_t buflen);
uint32_t xsave_area_size(uint64_t mask, bool compacted);
void x86_update_hflags(CPUX86State* env);
+int decompact_xsave_area(const void *buf, size_t buflen, CPUX86State *env);
+int compact_xsave_area(CPUX86State *env, void *buf, size_t buflen);
static inline bool hyperv_feat_enabled(X86CPU *cpu, int feat)
{
diff --git a/target/i386/xsave_helper.c b/target/i386/xsave_helper.c
index bab22587320..625bae103ab 100644
--- a/target/i386/xsave_helper.c
+++ b/target/i386/xsave_helper.c
@@ -3,6 +3,7 @@
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
+#include "qemu/error-report.h"
#include "cpu.h"
@@ -293,3 +294,258 @@ void x86_cpu_xrstor_all_areas(X86CPU *cpu, const void *buf, uint32_t buflen)
}
#endif
}
+
+#define XSTATE_BV_IN_HDR offsetof(X86XSaveHeader, xstate_bv)
+#define XCOMP_BV_IN_HDR offsetof(X86XSaveHeader, xcomp_bvo)
+
+typedef struct X86XSaveAreaView {
+ /* 512 bytes */
+ X86LegacyXSaveArea legacy;
+ /* 64 bytes */
+ X86XSaveHeader header;
+ /* ...followed by individual xsave areas */
+} X86XSaveAreaView;
+
+#define XSAVE_XSTATE_BV_OFFSET offsetof(X86XSaveAreaView, header.xstate_bv)
+#define XSAVE_XCOMP_BV_OFFSET offsetof(X86XSaveAreaView, header.xcomp_bv)
+#define XSAVE_EXT_OFFSET (sizeof(X86LegacyXSaveArea) + \
+ sizeof(X86XSaveHeader))
+
+/**
+ * decompact_xsave_area - Convert compacted XSAVE format to standard format
+ * @buf: Source buffer containing compacted XSAVE data
+ * @buflen: Size of source buffer
+ * @env: CPU state where the standard format buffer will be written to
+ *
+ * Accelerator backends like MSHV might return XSAVE state in compacted format
+ * (XSAVEC). The state components have to be packed contiguously without gaps.
+ * The XSAVE qemu buffers are in standard format where each component has a
+ * fixed offset.
+ *
+ * Returns: 0 on success, negative errno on failure
+ */
+int decompact_xsave_area(const void *buf, size_t buflen, CPUX86State *env)
+{
+ uint64_t compacted_xstate_bv, compacted_xcomp_bv, compacted_layout_bv;
+ size_t xsave_offset;
+ uint64_t *xcomp_bv;
+ size_t i;
+ uint32_t eax, ebx, ecx, edx;
+ uint32_t size, dst_off;
+ bool align64;
+ uint64_t guest_xcr0, *xstate_bv;
+
+ compacted_xstate_bv = *(uint64_t *)(buf + XSAVE_XSTATE_BV_OFFSET);
+ compacted_xcomp_bv = *(uint64_t *)(buf + XSAVE_XCOMP_BV_OFFSET);
+
+ /* This function only handles compacted format (bit 63 set) */
+ assert((compacted_xcomp_bv >> 63) & 1);
+
+ /* Low bits of XCOMP_BV describe which components are in the layout */
+ compacted_layout_bv = compacted_xcomp_bv & ~(1ULL << 63);
+
+ /* Zero out buffer, then copy legacy region (FP + SSE) and header as-is */
+ memset(env->xsave_buf, 0, env->xsave_buf_len);
+ memcpy(env->xsave_buf, buf, XSAVE_EXT_OFFSET);
+
+ /*
+ * We mask XSTATE_BV with the guest's supported XCR0 because:
+ * 1. Supervisor state (IA32_XSS) is hypervisor-managed, we don't use
+ * this state for migration.
+ * 2. Features disabled at partition creation (e.g. AMX) must be excluded
+ */
+ guest_xcr0 = ((uint64_t)env->features[FEAT_XSAVE_XCR0_HI] << 32) |
+ env->features[FEAT_XSAVE_XCR0_LO];
+ xstate_bv = (uint64_t *)(env->xsave_buf + XSAVE_XSTATE_BV_OFFSET);
+ *xstate_bv &= guest_xcr0;
+
+ /* Clear bit 63 - output is standard format, not compacted */
+ xcomp_bv = (uint64_t *)(env->xsave_buf + XSAVE_XCOMP_BV_OFFSET);
+ *xcomp_bv = *xcomp_bv & ~(1ULL << 63);
+
+ /*
+ * Process each extended state component in the compacted layout.
+ * Components 0 and 1 (FP and SSE) are in the legacy region, so we
+ * start at component 2. For each component:
+ * - Calculate its offset in the compacted source (contiguous layout)
+ * - Get its fixed offset in the standard destination from CPUID
+ * - Copy if the component has non-init state (bit set in XSTATE_BV)
+ */
+ xsave_offset = XSAVE_EXT_OFFSET;
+ for (i = 2; i < 63; i++) {
+ if (((compacted_layout_bv >> i) & 1) == 0) {
+ continue;
+ }
+
+ /* Query guest CPUID for this component's size and standard offset */
+ cpu_x86_cpuid(env, 0xD, i, &eax, &ebx, &ecx, &edx);
+
+ size = eax;
+ dst_off = ebx;
+ align64 = (ecx & (1u << 1)) != 0;
+
+ /* Component is in the layout but unknown to the guest CPUID model */
+ if (size == 0) {
+ /*
+ * The hypervisor might expose a component that has no
+ * representation in the guest CPUID model. We query the host to
+ * retrieve the size of the component, so we can skip over it.
+ */
+ host_cpuid(0xD, i, &eax, &ebx, &ecx, &edx);
+ size = eax;
+ align64 = (ecx & (1u << 1)) != 0;
+ if (size == 0) {
+ error_report("xsave component %zu: size unknown to both "
+ "guest and host CPUID", i);
+ return -EINVAL;
+ }
+
+ if (align64) {
+ xsave_offset = QEMU_ALIGN_UP(xsave_offset, 64);
+ }
+
+ if (xsave_offset + size > buflen) {
+ error_report("xsave component %zu overruns source buffer: "
+ "offset=%zu size=%u buflen=%zu",
+ i, xsave_offset, size, buflen);
+ return -E2BIG;
+ }
+
+ xsave_offset += size;
+ continue;
+ }
+
+ if (align64) {
+ xsave_offset = QEMU_ALIGN_UP(xsave_offset, 64);
+ }
+
+ if ((xsave_offset + size) > buflen) {
+ error_report("xsave component %zu overruns source buffer: "
+ "offset=%zu size=%u buflen=%zu",
+ i, xsave_offset, size, buflen);
+ return -E2BIG;
+ }
+
+ if ((dst_off + size) > env->xsave_buf_len) {
+ error_report("xsave component %zu overruns destination buffer: "
+ "offset=%u size=%u buflen=%zu",
+ i, dst_off, size, (size_t)env->xsave_buf_len);
+ return -E2BIG;
+ }
+
+ /* Copy components marked present in XSTATE_BV to guest model */
+ if (((compacted_xstate_bv >> i) & 1) != 0) {
+ memcpy(env->xsave_buf + dst_off, buf + xsave_offset, size);
+ }
+
+ xsave_offset += size;
+ }
+
+ return 0;
+}
+
+/**
+ * compact_xsave_area - Convert standard XSAVE format to compacted format
+ * @env: CPU state containing the standard format XSAVE buffer
+ * @buf: Destination buffer for compacted XSAVE data (to send to hypervisor)
+ * @buflen: Size of destination buffer
+ *
+ * Accelerator backends like MSHV might expect XSAVE state in compacted format
+ * (XSAVEC). The state components are packed contiguously without gaps.
+ * The XSAVE qemu buffers are in standard format where each component has a
+ * fixed offset.
+ *
+ * This function converts from standard to compacted format, it accepts a
+ * pre-allocated destination buffer of sufficient size, it is the
+ * responsibility of the caller to ensure the buffer is big enough.
+ *
+ * Returns: total size of compacted XSAVE data written to @buf
+ */
+int compact_xsave_area(CPUX86State *env, void *buf, size_t buflen)
+{
+ uint64_t *xcomp_bv;
+ size_t i;
+ uint32_t eax, ebx, ecx, edx;
+ uint32_t size, src_off;
+ bool align64;
+ size_t compact_offset;
+ uint64_t host_xcr0_mask, guest_xcr0;
+
+ /* Zero out buffer, then copy legacy region (FP + SSE) and header as-is */
+ memset(buf, 0, buflen);
+ memcpy(buf, env->xsave_buf, XSAVE_EXT_OFFSET);
+
+ /*
+ * Set XCOMP_BV to indicate compacted format (bit 63) and which
+ * components are in the layout.
+ *
+ * We must explicitly set XCOMP_BV because x86_cpu_xsave_all_areas()
+ * produces standard format with XCOMP_BV=0 (buffer is zeroed and only
+ * XSTATE_BV is set in the header).
+ *
+ * XCOMP_BV must reflect the partition's XSAVE capability, not the
+ * guest's current XCR0 (env->xcr0). These differ b/c:
+ * - A guest's XCR0 is what the guest OS has enabled via XSETBV
+ * - The partition's XCR0 mask is the hypervisor's save/restore capability
+ *
+ * The hypervisor uses XSAVES which saves based on its capability, so the
+ * XCOMP_BV value in the buffer we send back must match that capability.
+ *
+ * We intersect the host XCR0 with the guest's supported XCR0 features
+ * (FEAT_XSAVE_XCR0_*) so that features disabled at partition creation
+ * (e.g. AMX) are excluded from the compacted layout.
+ */
+ host_cpuid(0xD, 0, &eax, &ebx, &ecx, &edx);
+ host_xcr0_mask = ((uint64_t)edx << 32) | eax;
+ guest_xcr0 = ((uint64_t)env->features[FEAT_XSAVE_XCR0_HI] << 32) |
+ env->features[FEAT_XSAVE_XCR0_LO];
+ host_xcr0_mask &= guest_xcr0;
+ xcomp_bv = buf + XSAVE_XCOMP_BV_OFFSET;
+ *xcomp_bv = host_xcr0_mask | (1ULL << 63);
+
+ /*
+ * Process each extended state component in the host's XCR0.
+ * The compacted layout must match XCOMP_BV (host capability).
+ *
+ * For each component:
+ * - Get its size and standard offset from host CPUID
+ * - Apply 64-byte alignment if required
+ * - Copy data only if guest has this component (bit set in env->xcr0)
+ * - Always advance offset to maintain correct layout
+ */
+ compact_offset = XSAVE_EXT_OFFSET;
+ for (i = 2; i < 63; i++) {
+ if (!((host_xcr0_mask >> i) & 1)) {
+ continue;
+ }
+
+ /* Query host CPUID for this component's size and standard offset */
+ host_cpuid(0xD, i, &eax, &ebx, &ecx, &edx);
+ size = eax;
+ src_off = ebx;
+ align64 = (ecx >> 1) & 1;
+
+ if (size == 0) {
+ /* Component in host xcr0 but unknown - shouldn't happen */
+ continue;
+ }
+
+ /* Apply 64-byte alignment if required by this component */
+ if (align64) {
+ compact_offset = QEMU_ALIGN_UP(compact_offset, 64);
+ }
+
+ /*
+ * Only copy data if guest has this component enabled in XCR0.
+ * Otherwise the component remains zeroed (init state), but we
+ * still advance the offset to maintain the correct layout.
+ */
+ if ((env->xcr0 >> i) & 1) {
+ memcpy(buf + compact_offset, env->xsave_buf + src_off, size);
+ }
+
+ compact_offset += size;
+ }
+
+ return compact_offset;
+}
diff --git a/target/i386/meson.build b/target/i386/meson.build
index d385eafdf7e..14b1d2977d5 100644
--- a/target/i386/meson.build
+++ b/target/i386/meson.build
@@ -3,14 +3,13 @@ i386_ss.add(files(
'cpu.c',
'gdbstub.c',
'helper.c',
- 'xsave_helper.c',
'cpu-dump.c',
))
i386_ss.add(when: 'CONFIG_SEV', if_true: files('host-cpu.c', 'confidential-guest.c'))
# x86 cpu type
-i386_ss.add(when: 'CONFIG_KVM', if_true: files('host-cpu.c'))
-i386_ss.add(when: 'CONFIG_HVF', if_true: files('host-cpu.c'))
+i386_ss.add(when: 'CONFIG_KVM', if_true: files('xsave_helper.c', 'host-cpu.c'))
+i386_ss.add(when: 'CONFIG_HVF', if_true: files('xsave_helper.c', 'host-cpu.c'))
i386_ss.add(when: 'CONFIG_WHPX', if_true: files('host-cpu.c'))
i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c'))
i386_ss.add(when: 'CONFIG_MSHV', if_true: files('host-cpu.c'))
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 53/58] whpx: xsave support
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (50 preceding siblings ...)
2026-04-30 17:21 ` [PULL 52/58] target/i386: add de/compaction to xsave_helper Paolo Bonzini
@ 2026-04-30 17:21 ` Paolo Bonzini
2026-04-30 17:22 ` [PULL 54/58] whpx: i386: set APIC ID only when APIC present Paolo Bonzini
` (5 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:21 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-30-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-internal.h | 16 ++
target/i386/whpx/whpx-all.c | 379 ++++++++++++++++++++++++---------
target/i386/meson.build | 2 +-
3 files changed, 293 insertions(+), 104 deletions(-)
diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h
index 86639627b36..0aae83bd7c8 100644
--- a/include/system/whpx-internal.h
+++ b/include/system/whpx-internal.h
@@ -99,6 +99,22 @@ void whpx_apic_get(APICCommonState *s);
UINT32 StateSize)) \
X(HRESULT, WHvResetPartition, \
(WHV_PARTITION_HANDLE Partition)) \
+ X(HRESULT, WHvGetVirtualProcessorXsaveState, \
+ (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, \
+ PVOID Buffer, \
+ UINT32 BufferSizeInBytes, UINT32 *BytesWritten)) \
+ X(HRESULT, WHvSetVirtualProcessorXsaveState, \
+ (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, \
+ PVOID Buffer, \
+ UINT32 BufferSizeInBytes)) \
+ X(HRESULT, WHvGetVirtualProcessorState, \
+ (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, \
+ WHV_VIRTUAL_PROCESSOR_STATE_TYPE StateType, PVOID Buffer, \
+ UINT32 BufferSizeInBytes, UINT32 *BytesWritten)) \
+ X(HRESULT, WHvSetVirtualProcessorState, \
+ (WHV_PARTITION_HANDLE Partition, UINT32 VpIndex, \
+ WHV_VIRTUAL_PROCESSOR_STATE_TYPE StateType, PVOID Buffer, \
+ UINT32 BufferSizeInBytes)) \
LIST_WINHVPLATFORM_FUNCTIONS_SUPPLEMENTAL_ARCH(X)
#define WHP_DEFINE_TYPE(return_type, function_name, signature) \
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 8d8aabf4db7..717d47f9cce 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -10,6 +10,7 @@
#include "qemu/osdep.h"
#include "cpu.h"
+#include "qemu/typedefs.h"
#include "system/address-spaces.h"
#include "system/ioport.h"
#include "gdbstub/helpers.h"
@@ -20,6 +21,7 @@
#include "system/cpus.h"
#include "system/runstate.h"
#include "qemu/main-loop.h"
+#include "qemu/memalign.h"
#include "hw/core/boards.h"
#include "hw/intc/ioapic.h"
#include "hw/intc/i8259.h"
@@ -108,34 +110,6 @@ static const WHV_REGISTER_NAME whpx_register_names[] = {
* WHvX64RegisterDr7,
*/
- /* X64 Floating Point and Vector Registers */
- WHvX64RegisterXmm0,
- WHvX64RegisterXmm1,
- WHvX64RegisterXmm2,
- WHvX64RegisterXmm3,
- WHvX64RegisterXmm4,
- WHvX64RegisterXmm5,
- WHvX64RegisterXmm6,
- WHvX64RegisterXmm7,
- WHvX64RegisterXmm8,
- WHvX64RegisterXmm9,
- WHvX64RegisterXmm10,
- WHvX64RegisterXmm11,
- WHvX64RegisterXmm12,
- WHvX64RegisterXmm13,
- WHvX64RegisterXmm14,
- WHvX64RegisterXmm15,
- WHvX64RegisterFpMmx0,
- WHvX64RegisterFpMmx1,
- WHvX64RegisterFpMmx2,
- WHvX64RegisterFpMmx3,
- WHvX64RegisterFpMmx4,
- WHvX64RegisterFpMmx5,
- WHvX64RegisterFpMmx6,
- WHvX64RegisterFpMmx7,
- WHvX64RegisterFpControlStatus,
- WHvX64RegisterXmmControlStatus,
-
/* X64 MSRs */
WHvX64RegisterEfer,
#ifdef TARGET_X86_64
@@ -182,6 +156,36 @@ static const WHV_REGISTER_NAME whpx_register_names_for_vmexit[] = {
WHvX64RegisterR15,
};
+static const WHV_REGISTER_NAME whpx_register_names_legacy_fp[] = {
+ /* X64 Floating Point and Vector Registers (non-xsave) */
+ WHvX64RegisterXmm0,
+ WHvX64RegisterXmm1,
+ WHvX64RegisterXmm2,
+ WHvX64RegisterXmm3,
+ WHvX64RegisterXmm4,
+ WHvX64RegisterXmm5,
+ WHvX64RegisterXmm6,
+ WHvX64RegisterXmm7,
+ WHvX64RegisterXmm8,
+ WHvX64RegisterXmm9,
+ WHvX64RegisterXmm10,
+ WHvX64RegisterXmm11,
+ WHvX64RegisterXmm12,
+ WHvX64RegisterXmm13,
+ WHvX64RegisterXmm14,
+ WHvX64RegisterXmm15,
+ WHvX64RegisterFpMmx0,
+ WHvX64RegisterFpMmx1,
+ WHvX64RegisterFpMmx2,
+ WHvX64RegisterFpMmx3,
+ WHvX64RegisterFpMmx4,
+ WHvX64RegisterFpMmx5,
+ WHvX64RegisterFpMmx6,
+ WHvX64RegisterFpMmx7,
+ WHvX64RegisterFpControlStatus,
+ WHvX64RegisterXmmControlStatus,
+};
+
struct whpx_register_set {
WHV_REGISTER_VALUE values[RTL_NUMBER_OF(whpx_register_names)];
};
@@ -392,6 +396,123 @@ static int whpx_set_tsc(CPUState *cpu)
return 0;
}
+static bool whpx_is_xsave_enabled(CPUState *cpu)
+{
+ CPUX86State *env = &X86_CPU(cpu)->env;
+ return env->cr[4] & CR4_OSXSAVE_MASK;
+}
+
+static size_t whpx_get_xsave_max_len(void)
+{
+ return whpx_get_supported_cpuid(0xd, 0, R_ECX);
+}
+
+static int whpx_set_xsave_state(const CPUState *cpu)
+{
+ struct whpx_state *whpx = &whpx_global;
+ X86CPU *x86cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86cpu->env;
+ HRESULT hr;
+ void *xsavec_buf;
+ size_t page = qemu_real_host_page_size();
+ size_t xsavec_buf_len;
+
+ /* allocate and populate compacted buffer */
+ xsavec_buf_len = whpx_get_xsave_max_len();
+ xsavec_buf = qemu_memalign(page, xsavec_buf_len);
+
+ /* save registers to standard format buffer */
+ x86_cpu_xsave_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len);
+
+ /* store compacted version of xsave area in xsavec_buf */
+ compact_xsave_area(env, xsavec_buf, xsavec_buf_len);
+
+ if (!whpx_is_legacy_os()) {
+ hr = whp_dispatch.WHvSetVirtualProcessorState(
+ whpx->partition, cpu->cpu_index,
+ WHvVirtualProcessorStateTypeXsaveState,
+ xsavec_buf,
+ xsavec_buf_len);
+ } else {
+ hr = whp_dispatch.WHvSetVirtualProcessorXsaveState(
+ whpx->partition, cpu->cpu_index,
+ xsavec_buf,
+ xsavec_buf_len);
+ }
+
+ qemu_vfree(xsavec_buf);
+ if (FAILED(hr)) {
+ error_report("WHPX: Failed to get virtual processor context, hr=%08lx",
+ hr);
+ }
+
+ return 0;
+}
+
+static void whpx_set_legacy_fp_registers(CPUState *cpu, WHPXStateLevel level)
+{
+ struct whpx_state *whpx = &whpx_global;
+ X86CPU *x86_cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86_cpu->env;
+ struct whpx_register_set vcxt;
+ HRESULT hr;
+ int idx = 0;
+ int i;
+ int idx_next;
+
+ assert(cpu_is_stopped(cpu) || qemu_cpu_is_self(cpu));
+
+ /* 16 XMM registers */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterXmm0);
+ idx_next = idx + 16;
+ for (i = 0; i < sizeof(env->xmm_regs) / sizeof(ZMMReg); i += 1, idx += 1) {
+ vcxt.values[idx].Reg128.Low64 = env->xmm_regs[i].ZMM_Q(0);
+ vcxt.values[idx].Reg128.High64 = env->xmm_regs[i].ZMM_Q(1);
+ }
+ idx = idx_next;
+
+ /* 8 FP registers */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterFpMmx0);
+ for (i = 0; i < 8; i += 1, idx += 1) {
+ vcxt.values[idx].Fp.AsUINT128.Low64 = env->fpregs[i].mmx.MMX_Q(0);
+ /* vcxt.values[idx].Fp.AsUINT128.High64 =
+ env->fpregs[i].mmx.MMX_Q(1);
+ */
+ }
+
+ /* FP control status register */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterFpControlStatus);
+ vcxt.values[idx].FpControlStatus.FpControl = env->fpuc;
+ vcxt.values[idx].FpControlStatus.FpStatus =
+ (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11;
+ vcxt.values[idx].FpControlStatus.FpTag = 0;
+ for (i = 0; i < 8; ++i) {
+ vcxt.values[idx].FpControlStatus.FpTag |= (!env->fptags[i]) << i;
+ }
+ vcxt.values[idx].FpControlStatus.Reserved = 0;
+ vcxt.values[idx].FpControlStatus.LastFpOp = env->fpop;
+ vcxt.values[idx].FpControlStatus.LastFpRip = env->fpip;
+ idx += 1;
+
+ /* XMM control status register */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterXmmControlStatus);
+ vcxt.values[idx].XmmControlStatus.LastFpRdp = 0;
+ vcxt.values[idx].XmmControlStatus.XmmStatusControl = env->mxcsr;
+ vcxt.values[idx].XmmControlStatus.XmmStatusControlMask = 0x0000ffff;
+ idx += 1;
+
+ hr = whp_dispatch.WHvSetVirtualProcessorRegisters(
+ whpx->partition, cpu->cpu_index,
+ whpx_register_names_legacy_fp,
+ idx,
+ &vcxt.values[0]);
+
+ if (FAILED(hr)) {
+ error_report("WHPX: Failed to set virtual processor context, hr=%08lx",
+ hr);
+ }
+}
+
void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
{
struct whpx_state *whpx = &whpx_global;
@@ -491,45 +612,11 @@ void whpx_set_registers(CPUState *cpu, WHPXStateLevel level)
*/
whpx_set_xcrs(cpu);
- /* 16 XMM registers */
- assert(whpx_register_names[idx] == WHvX64RegisterXmm0);
- idx_next = idx + 16;
- for (i = 0; i < sizeof(env->xmm_regs) / sizeof(ZMMReg); i += 1, idx += 1) {
- vcxt.values[idx].Reg128.Low64 = env->xmm_regs[i].ZMM_Q(0);
- vcxt.values[idx].Reg128.High64 = env->xmm_regs[i].ZMM_Q(1);
+ if (whpx_is_xsave_enabled(cpu)) {
+ whpx_set_xsave_state(cpu);
+ } else {
+ whpx_set_legacy_fp_registers(cpu, level);
}
- idx = idx_next;
-
- /* 8 FP registers */
- assert(whpx_register_names[idx] == WHvX64RegisterFpMmx0);
- for (i = 0; i < 8; i += 1, idx += 1) {
- vcxt.values[idx].Fp.AsUINT128.Low64 = env->fpregs[i].mmx.MMX_Q(0);
- /* vcxt.values[idx].Fp.AsUINT128.High64 =
- env->fpregs[i].mmx.MMX_Q(1);
- */
- }
-
- /* FP control status register */
- assert(whpx_register_names[idx] == WHvX64RegisterFpControlStatus);
- vcxt.values[idx].FpControlStatus.FpControl = env->fpuc;
- vcxt.values[idx].FpControlStatus.FpStatus =
- (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11;
- vcxt.values[idx].FpControlStatus.FpTag = 0;
- for (i = 0; i < 8; ++i) {
- vcxt.values[idx].FpControlStatus.FpTag |= (!env->fptags[i]) << i;
- }
- vcxt.values[idx].FpControlStatus.Reserved = 0;
- vcxt.values[idx].FpControlStatus.LastFpOp = env->fpop;
- vcxt.values[idx].FpControlStatus.LastFpRip = env->fpip;
- idx += 1;
-
- /* XMM control status register */
- assert(whpx_register_names[idx] == WHvX64RegisterXmmControlStatus);
- vcxt.values[idx].XmmControlStatus.LastFpRdp = 0;
- vcxt.values[idx].XmmControlStatus.XmmStatusControl = env->mxcsr;
- vcxt.values[idx].XmmControlStatus.XmmStatusControlMask = 0x0000ffff;
- idx += 1;
-
/* MSRs */
assert(whpx_register_names[idx] == WHvX64RegisterEfer);
vcxt.values[idx++].Reg64 = env->efer;
@@ -662,6 +749,110 @@ static void whpx_get_registers_for_vmexit(CPUState *cpu, WHPXStateLevel level)
x86_update_hflags(env);
}
+static void whpx_get_legacy_fp_registers(CPUState *cpu, WHPXStateLevel level)
+{
+ struct whpx_state *whpx = &whpx_global;
+ X86CPU *x86_cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86_cpu->env;
+ struct whpx_register_set vcxt;
+ HRESULT hr;
+ int i;
+ int idx;
+ int idx_next;
+
+ assert(cpu_is_stopped(cpu) || qemu_cpu_is_self(cpu));
+
+ hr = whp_dispatch.WHvGetVirtualProcessorRegisters(
+ whpx->partition, cpu->cpu_index,
+ whpx_register_names_legacy_fp,
+ RTL_NUMBER_OF(whpx_register_names_legacy_fp),
+ &vcxt.values[0]);
+
+ if (FAILED(hr)) {
+ error_report("WHPX: Failed to get virtual processor context, hr=%08lx",
+ hr);
+ }
+
+ idx = 0;
+ /* 16 XMM registers */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterXmm0);
+ idx_next = idx + 16;
+ for (i = 0; i < sizeof(env->xmm_regs) / sizeof(ZMMReg); i += 1, idx += 1) {
+ env->xmm_regs[i].ZMM_Q(0) = vcxt.values[idx].Reg128.Low64;
+ env->xmm_regs[i].ZMM_Q(1) = vcxt.values[idx].Reg128.High64;
+ }
+ idx = idx_next;
+
+ /* 8 FP registers */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterFpMmx0);
+ for (i = 0; i < 8; i += 1, idx += 1) {
+ env->fpregs[i].mmx.MMX_Q(0) = vcxt.values[idx].Fp.AsUINT128.Low64;
+ /* env->fpregs[i].mmx.MMX_Q(1) =
+ vcxt.values[idx].Fp.AsUINT128.High64;
+ */
+ }
+
+ /* FP control status register */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterFpControlStatus);
+ env->fpuc = vcxt.values[idx].FpControlStatus.FpControl;
+ env->fpstt = (vcxt.values[idx].FpControlStatus.FpStatus >> 11) & 0x7;
+ env->fpus = vcxt.values[idx].FpControlStatus.FpStatus & ~0x3800;
+ for (i = 0; i < 8; ++i) {
+ env->fptags[i] = !((vcxt.values[idx].FpControlStatus.FpTag >> i) & 1);
+ }
+ env->fpop = vcxt.values[idx].FpControlStatus.LastFpOp;
+ env->fpip = vcxt.values[idx].FpControlStatus.LastFpRip;
+ idx += 1;
+
+ /* XMM control status register */
+ assert(whpx_register_names_legacy_fp[idx] == WHvX64RegisterXmmControlStatus);
+ env->mxcsr = vcxt.values[idx].XmmControlStatus.XmmStatusControl;
+ idx += 1;
+}
+
+static int whpx_get_xsave_state(CPUState *cpu)
+{
+ struct whpx_state *whpx = &whpx_global;
+ X86CPU *x86cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86cpu->env;
+ int ret;
+ HRESULT hr;
+ void *xsavec_buf;
+ const size_t page = qemu_real_host_page_size();
+ size_t xsavec_buf_len = whpx_get_xsave_max_len();
+ UINT32 bytes_written;
+
+ xsavec_buf = qemu_memalign(page, xsavec_buf_len);
+ memset(xsavec_buf, 0, xsavec_buf_len);
+
+ if (!whpx_is_legacy_os()) {
+ hr = whp_dispatch.WHvGetVirtualProcessorState(
+ whpx->partition, cpu->cpu_index,
+ WHvVirtualProcessorStateTypeXsaveState,
+ xsavec_buf,
+ xsavec_buf_len, &bytes_written);
+ } else {
+ hr = whp_dispatch.WHvGetVirtualProcessorXsaveState(
+ whpx->partition, cpu->cpu_index,
+ xsavec_buf,
+ xsavec_buf_len, &bytes_written);
+ }
+ if (FAILED(hr) || bytes_written == 0) {
+ error_report("failed to get xsave state: %s", strerror(errno));
+ return -errno;
+ }
+
+ ret = decompact_xsave_area(xsavec_buf, xsavec_buf_len, env);
+ qemu_vfree(xsavec_buf);
+ if (ret < 0) {
+ error_report("failed to decompact xsave area");
+ return ret;
+ }
+ x86_cpu_xrstor_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len);
+
+ return 0;
+}
+
void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
{
struct whpx_state *whpx = &whpx_global;
@@ -758,40 +949,11 @@ void whpx_get_registers(CPUState *cpu, WHPXStateLevel level)
*/
whpx_get_xcrs(cpu);
- /* 16 XMM registers */
- assert(whpx_register_names[idx] == WHvX64RegisterXmm0);
- idx_next = idx + 16;
- for (i = 0; i < sizeof(env->xmm_regs) / sizeof(ZMMReg); i += 1, idx += 1) {
- env->xmm_regs[i].ZMM_Q(0) = vcxt.values[idx].Reg128.Low64;
- env->xmm_regs[i].ZMM_Q(1) = vcxt.values[idx].Reg128.High64;
+ if (whpx_is_xsave_enabled(cpu)) {
+ whpx_get_xsave_state(cpu);
+ } else {
+ whpx_get_legacy_fp_registers(cpu, level);
}
- idx = idx_next;
-
- /* 8 FP registers */
- assert(whpx_register_names[idx] == WHvX64RegisterFpMmx0);
- for (i = 0; i < 8; i += 1, idx += 1) {
- env->fpregs[i].mmx.MMX_Q(0) = vcxt.values[idx].Fp.AsUINT128.Low64;
- /* env->fpregs[i].mmx.MMX_Q(1) =
- vcxt.values[idx].Fp.AsUINT128.High64;
- */
- }
-
- /* FP control status register */
- assert(whpx_register_names[idx] == WHvX64RegisterFpControlStatus);
- env->fpuc = vcxt.values[idx].FpControlStatus.FpControl;
- env->fpstt = (vcxt.values[idx].FpControlStatus.FpStatus >> 11) & 0x7;
- env->fpus = vcxt.values[idx].FpControlStatus.FpStatus & ~0x3800;
- for (i = 0; i < 8; ++i) {
- env->fptags[i] = !((vcxt.values[idx].FpControlStatus.FpTag >> i) & 1);
- }
- env->fpop = vcxt.values[idx].FpControlStatus.LastFpOp;
- env->fpip = vcxt.values[idx].FpControlStatus.LastFpRip;
- idx += 1;
-
- /* XMM control status register */
- assert(whpx_register_names[idx] == WHvX64RegisterXmmControlStatus);
- env->mxcsr = vcxt.values[idx].XmmControlStatus.XmmStatusControl;
- idx += 1;
/* MSRs */
assert(whpx_register_names[idx] == WHvX64RegisterEfer);
@@ -1586,6 +1748,9 @@ void whpx_arch_destroy_vcpu(CPUState *cpu)
X86CPU *x86cpu = X86_CPU(cpu);
CPUX86State *env = &x86cpu->env;
g_free(env->emu_mmio_buf);
+ qemu_vfree(env->xsave_buf);
+ env->xsave_buf = NULL;
+ env->xsave_buf_len = 0;
}
/* Returns the address of the next instruction that is about to be executed. */
@@ -2446,6 +2611,9 @@ int whpx_init_vcpu(CPUState *cpu)
Error *local_error = NULL;
X86CPU *x86_cpu = X86_CPU(cpu);
CPUX86State *env = &x86_cpu->env;
+ X86XSaveHeader *header;
+ size_t page_size = qemu_real_host_page_size();
+ size_t xsave_len;
UINT64 freq = 0;
int ret;
@@ -2522,6 +2690,15 @@ int whpx_init_vcpu(CPUState *cpu)
qemu_add_vm_change_state_handler(whpx_cpu_update_state, env);
env->emu_mmio_buf = g_new(char, 4096);
+ /* Initialize XSAVE buffer page-aligned */
+ xsave_len = whpx_get_xsave_max_len();
+ env->xsave_buf = qemu_memalign(page_size, xsave_len);
+ env->xsave_buf_len = xsave_len;
+ memset(env->xsave_buf, 0, env->xsave_buf_len);
+
+ /* we need to set the compacted format bit in xsave header for Hyper-V */
+ header = (X86XSaveHeader *)(env->xsave_buf + sizeof(X86LegacyXSaveArea));
+ header->xcomp_bv = header->xstate_bv | (1ULL << 63);
return 0;
@@ -2722,10 +2899,6 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
error_report("WHPX: Failed to query XSAVE capability, hr=%08lx", hr);
}
- if (!whpx_has_xsave()) {
- printf("WHPX: Partition is not XSAVE capable\n");
- }
-
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
prop.ProcessorCount = ms->smp.cpus;
hr = whp_dispatch.WHvSetPartitionProperty(
diff --git a/target/i386/meson.build b/target/i386/meson.build
index 14b1d2977d5..80062d1d0d8 100644
--- a/target/i386/meson.build
+++ b/target/i386/meson.build
@@ -10,7 +10,7 @@ i386_ss.add(when: 'CONFIG_SEV', if_true: files('host-cpu.c', 'confidential-guest
# x86 cpu type
i386_ss.add(when: 'CONFIG_KVM', if_true: files('xsave_helper.c', 'host-cpu.c'))
i386_ss.add(when: 'CONFIG_HVF', if_true: files('xsave_helper.c', 'host-cpu.c'))
-i386_ss.add(when: 'CONFIG_WHPX', if_true: files('host-cpu.c'))
+i386_ss.add(when: 'CONFIG_WHPX', if_true: files('xsave_helper.c', 'host-cpu.c'))
i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c'))
i386_ss.add(when: 'CONFIG_MSHV', if_true: files('host-cpu.c'))
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 54/58] whpx: i386: set APIC ID only when APIC present
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (51 preceding siblings ...)
2026-04-30 17:21 ` [PULL 53/58] whpx: xsave support Paolo Bonzini
@ 2026-04-30 17:22 ` Paolo Bonzini
2026-04-30 17:22 ` [PULL 55/58] whpx: i386: update migration blocker message Paolo Bonzini
` (4 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:22 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
-M isapc doesn't have an APIC
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-31-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 717d47f9cce..ce04d6b06f9 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2643,7 +2643,7 @@ int whpx_init_vcpu(CPUState *cpu)
goto error;
}
- if (!whpx_irqchip_in_kernel()) {
+ if (!whpx_irqchip_in_kernel() && x86_cpu->apic_state != NULL) {
WHV_REGISTER_VALUE apic_id = {.Reg64 = x86_cpu->apic_state->initial_apic_id};
whpx_set_reg(cpu, WHvX64RegisterInitialApicId, apic_id);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 55/58] whpx: i386: update migration blocker message
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (52 preceding siblings ...)
2026-04-30 17:22 ` [PULL 54/58] whpx: i386: set APIC ID only when APIC present Paolo Bonzini
@ 2026-04-30 17:22 ` Paolo Bonzini
2026-04-30 17:22 ` [PULL 56/58] whpx: i386: add feature to intercept #GP MSR accesses Paolo Bonzini
` (3 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:22 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Quite a part of it is from older times...
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-32-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
target/i386/whpx/whpx-all.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index ce04d6b06f9..5750539ee49 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2622,8 +2622,8 @@ int whpx_init_vcpu(CPUState *cpu)
*/
if (whpx_migration_blocker == NULL) {
error_setg(&whpx_migration_blocker,
- "State blocked due to non-migratable CPUID feature support,"
- "dirty memory tracking support, and XSAVE/XRSTOR support");
+ "State blocked due to missing dirty memory tracking support,"
+ "And some system register/state save-restore ");
if (migrate_add_blocker(&whpx_migration_blocker, &local_error) < 0) {
error_report_err(local_error);
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 56/58] whpx: i386: add feature to intercept #GP MSR accesses
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (53 preceding siblings ...)
2026-04-30 17:22 ` [PULL 55/58] whpx: i386: update migration blocker message Paolo Bonzini
@ 2026-04-30 17:22 ` Paolo Bonzini
2026-04-30 17:22 ` [PULL 57/58] whpx: i386: add SeparateSecurityDomain flag and make default Paolo Bonzini
` (2 subsequent siblings)
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:22 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
It turns out they're not that uncommon, so have
a feature around to log those.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-35-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-internal.h | 1 +
accel/whpx/whpx-common.c | 1 +
target/i386/whpx/whpx-all.c | 183 +++++++++++++++++++++++++++++----
3 files changed, 166 insertions(+), 19 deletions(-)
diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h
index 0aae83bd7c8..15027a7d524 100644
--- a/include/system/whpx-internal.h
+++ b/include/system/whpx-internal.h
@@ -48,6 +48,7 @@ struct whpx_state {
bool hyperv_enlightenments_enabled;
bool ignore_unknown_msr;
+ bool intercept_msr_gp;
};
extern struct whpx_state whpx_global;
diff --git a/accel/whpx/whpx-common.c b/accel/whpx/whpx-common.c
index 497c03138ec..d846e08714b 100644
--- a/accel/whpx/whpx-common.c
+++ b/accel/whpx/whpx-common.c
@@ -555,6 +555,7 @@ static void whpx_accel_instance_init(Object *obj)
/* Value determined at whpx_accel_init */
whpx->hyperv_enlightenments_enabled = false;
whpx->ignore_unknown_msr = true;
+ whpx->intercept_msr_gp = false;
}
static const TypeInfo whpx_accel_type = {
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index 5750539ee49..d6bc36686c2 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -1008,6 +1008,27 @@ static int emulate_instruction(CPUState *cpu, const uint8_t *insn_bytes, size_t
return 0;
}
+static int emulate_msr_instruction(CPUState *cpu,
+ const uint8_t *insn_bytes, size_t insn_len)
+{
+ X86CPU *x86_cpu = X86_CPU(cpu);
+ CPUX86State *env = &x86_cpu->env;
+ struct x86_decode decode = { 0 };
+ x86_insn_stream stream = { .bytes = insn_bytes, .len = insn_len };
+
+ whpx_get_registers(cpu, WHPX_LEVEL_FAST_RUNTIME_STATE);
+ decode_instruction_stream(env, &decode, &stream);
+
+ if (decode.cmd != X86_DECODE_CMD_RDMSR
+ && decode.cmd != X86_DECODE_CMD_WRMSR) {
+ return 1;
+ }
+
+ exec_instruction(env, &decode);
+ whpx_set_registers(cpu, WHPX_LEVEL_FAST_RUNTIME_STATE);
+ return 0;
+}
+
static int whpx_handle_mmio(CPUState *cpu, WHV_RUN_VP_EXIT_CONTEXT *exit_ctx)
{
WHV_MEMORY_ACCESS_CONTEXT *ctx = &exit_ctx->MemoryAccess;
@@ -1022,6 +1043,45 @@ static int whpx_handle_mmio(CPUState *cpu, WHV_RUN_VP_EXIT_CONTEXT *exit_ctx)
return 0;
}
+static int whpx_handle_msr_from_gpf(CPUState *cpu)
+{
+ WHV_VP_EXCEPTION_CONTEXT *ctx = &cpu->accel->exit_ctx.VpException;
+ int ret;
+
+ ret = emulate_msr_instruction(cpu, ctx->InstructionBytes, ctx->InstructionByteCount);
+ if (ret == 1) {
+ /* Not an MSR instruction */
+ return 1;
+ }
+
+ return 0;
+}
+
+static void whpx_inject_back_gpf(CPUState *cpu)
+{
+ WHV_VP_EXCEPTION_CONTEXT *ctx = &cpu->accel->exit_ctx.VpException;
+ WHV_REGISTER_VALUE reg = {};
+
+ if (ctx->ExceptionInfo.SoftwareException) {
+ /* TODO */
+ warn_report("Was asked to inject software exception.");
+ return;
+ }
+
+ if (ctx->ExceptionType != EXCP0D_GPF) {
+ warn_report("Was asked to inject exception other than GPF.");
+ return;
+ }
+
+ reg.ExceptionEvent.EventPending = 1;
+ reg.ExceptionEvent.EventType = WHvX64PendingEventException;
+ reg.ExceptionEvent.DeliverErrorCode = ctx->ExceptionInfo.ErrorCodeValid;
+ reg.ExceptionEvent.Vector = ctx->ExceptionType;
+ reg.ExceptionEvent.ErrorCode = ctx->ErrorCode;
+ reg.ExceptionEvent.ExceptionParameter = ctx->ExceptionParameter;
+ whpx_set_reg(cpu, WHvRegisterPendingEvent, reg);
+}
+
static void handle_io(CPUState *env, uint16_t port, void *buffer,
int direction, int size, int count)
{
@@ -1210,13 +1270,54 @@ static target_ulong read_cr(CPUState *cpu, int cr)
return val.Reg64;
}
+static bool whpx_simulate_rdmsr(CPUState *cs)
+{
+ X86CPU *cpu = X86_CPU(cs);
+ CPUX86State *env = &cpu->env;
+ uint32_t msr = ECX(env);
+ uint64_t val = 0;
+
+ switch (msr) {
+ default:
+ error_report("WHPX: unknown msr 0x%x", msr);
+ x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ return 1;
+ break;
+ }
+
+ RAX(env) = (uint32_t)val;
+ RDX(env) = (uint32_t)(val >> 32);
+
+ return 0;
+}
+
+static bool whpx_simulate_wrmsr(CPUState *cs)
+{
+ X86CPU *cpu = X86_CPU(cs);
+ CPUX86State *env = &cpu->env;
+ uint32_t msr = ECX(env);
+ uint64_t data = ((uint64_t)EDX(env) << 32) | EAX(env);
+
+ switch (msr) {
+ default:
+ error_report("WHPX: unknown msr 0x%x val %llx", msr, data);
+ x86_emul_raise_exception(&X86_CPU(cpu)->env, EXCP0D_GPF, 0);
+ return 1;
+ break;
+ }
+
+ return 0;
+}
+
static const struct x86_emul_ops whpx_x86_emul_ops = {
.read_segment_descriptor = read_segment_descriptor,
.handle_io = handle_io,
.is_protected_mode = is_protected_mode,
.is_long_mode = is_long_mode,
.is_user_mode = is_user_mode,
- .read_cr = read_cr
+ .read_cr = read_cr,
+ .simulate_rdmsr = whpx_simulate_rdmsr,
+ .simulate_wrmsr = whpx_simulate_wrmsr
};
static void whpx_init_emu(void)
@@ -1356,6 +1457,18 @@ uint64_t whpx_get_supported_msr_feature(uint32_t index)
return 0;
}
+static UINT64 whpx_get_default_exceptions(void)
+{
+ struct whpx_state *whpx = &whpx_global;
+ UINT64 intercepts = 0;
+
+ if (whpx->intercept_msr_gp) {
+ intercepts |= 1UL << WHvX64ExceptionTypeGeneralProtectionFault;
+ }
+
+ return intercepts;
+}
+
/*
* Controls whether we should intercept various exceptions on the guest,
* namely breakpoint/single-step events.
@@ -1378,7 +1491,7 @@ HRESULT whpx_set_exception_exit_bitmap(UINT64 exceptions)
prop.ExtendedVmExits.X64MsrExit = 1;
prop.ExtendedVmExits.X64CpuidExit = 1;
- if (exceptions != 0) {
+ if (exceptions != 0 || whpx_get_default_exceptions() != 0) {
prop.ExtendedVmExits.ExceptionExit = 1;
}
@@ -1393,7 +1506,7 @@ HRESULT whpx_set_exception_exit_bitmap(UINT64 exceptions)
}
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
- prop.ExceptionExitBitmap = exceptions;
+ prop.ExceptionExitBitmap = exceptions | whpx_get_default_exceptions();
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
@@ -1403,6 +1516,8 @@ HRESULT whpx_set_exception_exit_bitmap(UINT64 exceptions)
if (SUCCEEDED(hr)) {
whpx->exception_exit_bitmap = exceptions;
+ } else {
+ error_report("WHPX: Failed to set exception exit bitmap, hr=%08lx", hr);
}
return hr;
@@ -2518,6 +2633,15 @@ int whpx_vcpu_run(CPUState *cpu)
break;
}
case WHvRunVpExitReasonException:
+ if (vcpu->exit_ctx.VpException.ExceptionType ==
+ WHvX64ExceptionTypeGeneralProtectionFault) {
+ if (whpx_handle_msr_from_gpf(cpu)) {
+ whpx_inject_back_gpf(cpu);
+ }
+ ret = 0;
+ break;
+ }
+
whpx_get_registers(cpu, WHPX_LEVEL_FULL_STATE);
if ((vcpu->exit_ctx.VpException.ExceptionType ==
@@ -2806,6 +2930,38 @@ static void whpx_set_unknown_msr(Object *obj, Visitor *v,
}
}
+static void whpx_set_intercept_msr_gp(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ struct whpx_state *whpx = &whpx_global;
+ OnOffAuto mode;
+
+ if (!visit_type_OnOffAuto(v, name, &mode, errp)) {
+ return;
+ }
+
+ switch (mode) {
+ case ON_OFF_AUTO_ON:
+ whpx->intercept_msr_gp = true;
+ break;
+
+ case ON_OFF_AUTO_OFF:
+ whpx->intercept_msr_gp = false;
+ break;
+
+ case ON_OFF_AUTO_AUTO:
+ whpx->intercept_msr_gp = false;
+ break;
+ default:
+ /*
+ * The value was checked in visit_type_OnOffAuto() above. If
+ * we get here, then something is wrong in QEMU.
+ */
+ abort();
+ }
+}
+
void whpx_arch_accel_class_init(ObjectClass *oc)
{
object_class_property_add(oc, "ignore-unknown-msr", "OnOffAuto",
@@ -2813,6 +2969,11 @@ void whpx_arch_accel_class_init(ObjectClass *oc)
NULL, NULL);
object_class_property_set_description(oc, "ignore-unknown-msr",
"Configure unknown MSR behavior");
+ object_class_property_add(oc, "intercept-msr-gp", "OnOffAuto",
+ NULL, whpx_set_intercept_msr_gp,
+ NULL, NULL);
+ object_class_property_set_description(oc, "intercept-msr-gp",
+ "Intercept #GP to log erroring MSR accesses.");
}
int whpx_accel_init(AccelState *as, MachineState *ms)
@@ -3067,22 +3228,6 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
goto error;
}
- /* Register for MSR and CPUID exits */
- memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
- prop.ExtendedVmExits.X64MsrExit = 1;
- prop.ExtendedVmExits.X64CpuidExit = 1;
-
- hr = whp_dispatch.WHvSetPartitionProperty(
- whpx->partition,
- WHvPartitionPropertyCodeExtendedVmExits,
- &prop,
- sizeof(WHV_PARTITION_PROPERTY));
- if (FAILED(hr)) {
- error_report("WHPX: Failed to enable extended VM exits, hr=%08lx", hr);
- ret = -EINVAL;
- goto error;
- }
-
memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
prop.X64MsrExitBitmap.UnhandledMsrs = 1;
prop.X64MsrExitBitmap.ApicBaseMsrWrite = 1;
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 57/58] whpx: i386: add SeparateSecurityDomain flag and make default
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (54 preceding siblings ...)
2026-04-30 17:22 ` [PULL 56/58] whpx: i386: add feature to intercept #GP MSR accesses Paolo Bonzini
@ 2026-04-30 17:22 ` Paolo Bonzini
2026-04-30 17:22 ` [PULL 58/58] whpx: i386: documentation update Paolo Bonzini
2026-05-02 10:49 ` [PULL 00/58] Misc patches for 2026-04-30 Stefan Hajnoczi
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:22 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
For workloads where isolation is less important, -accel whpx,ssd=off
will provide significantly higher MMIO performance.
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-37-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
include/system/whpx-internal.h | 2 ++
accel/whpx/whpx-common.c | 1 +
target/i386/whpx/whpx-all.c | 64 ++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+)
diff --git a/include/system/whpx-internal.h b/include/system/whpx-internal.h
index 15027a7d524..c295c5a5290 100644
--- a/include/system/whpx-internal.h
+++ b/include/system/whpx-internal.h
@@ -47,6 +47,8 @@ struct whpx_state {
bool hyperv_enlightenments_required;
bool hyperv_enlightenments_enabled;
+ bool separate_security_domain;
+
bool ignore_unknown_msr;
bool intercept_msr_gp;
};
diff --git a/accel/whpx/whpx-common.c b/accel/whpx/whpx-common.c
index d846e08714b..247e12db812 100644
--- a/accel/whpx/whpx-common.c
+++ b/accel/whpx/whpx-common.c
@@ -556,6 +556,7 @@ static void whpx_accel_instance_init(Object *obj)
whpx->hyperv_enlightenments_enabled = false;
whpx->ignore_unknown_msr = true;
whpx->intercept_msr_gp = false;
+ whpx->separate_security_domain = true;
}
static const TypeInfo whpx_accel_type = {
diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c
index d6bc36686c2..9d0c391e36f 100644
--- a/target/i386/whpx/whpx-all.c
+++ b/target/i386/whpx/whpx-all.c
@@ -2962,6 +2962,39 @@ static void whpx_set_intercept_msr_gp(Object *obj, Visitor *v,
}
}
+static void whpx_set_ssd(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ struct whpx_state *whpx = &whpx_global;
+ OnOffAuto mode;
+
+ if (!visit_type_OnOffAuto(v, name, &mode, errp)) {
+ return;
+ }
+
+ switch (mode) {
+ case ON_OFF_AUTO_ON:
+ whpx->separate_security_domain = true;
+ break;
+
+ case ON_OFF_AUTO_OFF:
+ whpx->separate_security_domain = false;
+ break;
+
+ case ON_OFF_AUTO_AUTO:
+ whpx->separate_security_domain = true;
+ break;
+ default:
+ /*
+ * The value was checked in visit_type_OnOffAuto() above. If
+ * we get here, then something is wrong in QEMU.
+ */
+ abort();
+ }
+}
+
+
void whpx_arch_accel_class_init(ObjectClass *oc)
{
object_class_property_add(oc, "ignore-unknown-msr", "OnOffAuto",
@@ -2974,6 +3007,11 @@ void whpx_arch_accel_class_init(ObjectClass *oc)
NULL, NULL);
object_class_property_set_description(oc, "intercept-msr-gp",
"Intercept #GP to log erroring MSR accesses.");
+ object_class_property_add(oc, "ssd", "OnOffAuto",
+ NULL, whpx_set_ssd,
+ NULL, NULL);
+ object_class_property_set_description(oc, "ssd",
+ "Separate security domain");
}
int whpx_accel_init(AccelState *as, MachineState *ms)
@@ -3169,6 +3207,32 @@ int whpx_accel_init(AccelState *as, MachineState *ms)
}
}
+ /*
+ * The combination of separate security domain off
+ * and disabling specifically these features results
+ * in a significant vmexit performance improvement
+ * by skipping speculative execution mitigations.
+ */
+ if (!whpx->separate_security_domain) {
+ processor_features.Bank0.IbrsSupport = 0;
+ processor_features.Bank0.StibpSupport = 0;
+ processor_features.Bank0.IbpbSupport = 0;
+ processor_features.Bank0.SsbdSupport = 0;
+ processor_features.Bank0.IbrsAllSupport = 0;
+ processor_features.Bank1.PsfdSupport = 0;
+ memset(&prop, 0, sizeof(WHV_PARTITION_PROPERTY));
+ prop.SeparateSecurityDomain = 0;
+ hr = whp_dispatch.WHvSetPartitionProperty(
+ whpx->partition,
+ WHvPartitionPropertyCodeSeparateSecurityDomain,
+ &prop,
+ sizeof(WHV_PARTITION_PROPERTY));
+ if (FAILED(hr)) {
+ error_report("WHPX: failed to unset separate security domain, hr=%08lx", hr);
+ /* Some old Windows 10 releases didn't have this, so not fatal*/
+ }
+ }
+
hr = whp_dispatch.WHvSetPartitionProperty(
whpx->partition,
WHvPartitionPropertyCodeProcessorFeaturesBanks,
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PULL 58/58] whpx: i386: documentation update
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (55 preceding siblings ...)
2026-04-30 17:22 ` [PULL 57/58] whpx: i386: add SeparateSecurityDomain flag and make default Paolo Bonzini
@ 2026-04-30 17:22 ` Paolo Bonzini
2026-05-02 10:49 ` [PULL 00/58] Misc patches for 2026-04-30 Stefan Hajnoczi
57 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-04-30 17:22 UTC (permalink / raw)
To: qemu-devel; +Cc: Mohamed Mediouni
From: Mohamed Mediouni <mohamed@unpredictable.fr>
Signed-off-by: Mohamed Mediouni <mohamed@unpredictable.fr>
Link: https://lore.kernel.org/r/20260422214225.2242-38-mohamed@unpredictable.fr
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
docs/system/whpx.rst | 40 +++++++++++-----------------------------
1 file changed, 11 insertions(+), 29 deletions(-)
diff --git a/docs/system/whpx.rst b/docs/system/whpx.rst
index 3e1979028c9..9909e86831b 100644
--- a/docs/system/whpx.rst
+++ b/docs/system/whpx.rst
@@ -63,7 +63,7 @@ additional functionality compared to ``-device ramfb``, but is
incompatible with Windows's UEFI GOP implementation, which
expects a linear framebuffer to be available.
-Some tracing options
+Accelerator options
--------------------
x86_64
@@ -75,6 +75,11 @@ to undocumented MSRs.
``-d invalid_mem`` allows to trace accesses to unmapped
GPAs.
+``-accel whpx,ssd=off`` disables the separate security domain feature,
+as in a BTB flush when entering/exiting the guest. This results in a
+significant MMIO performance increase at the detriment of security
+mitigations.
+
Known issues on x86_64
----------------------
@@ -96,45 +101,22 @@ MMX, SSE or AVX instructions for access to MMIO memory ranges.
Attempts to run such guests will result in an ``Unimplemented handler``
warning for MMX and a failure to decode for newer instructions.
-``-M isapc``
-^^^^^^^^^^^^
-
-``-M isapc`` doesn't disable the Hyper-V LAPIC on its own yet. To
-be able to use that machine, use ``-accel whpx,hyperv=off,kernel-irqchip=off``.
-
-However, in QEMU 11.0, the guest will still be a 64-bit x86
-ISA machine with all the corresponding CPUID leaves exposed.
-
-gdbstub
-^^^^^^^
-
-As save/restore of xsave state is not currently present, state
-exposed through GDB will be incomplete.
-
-The same also applies to ``info registers``.
-
-``-cpu type`` ignored
-^^^^^^^^^^^^^^^^^^^^^
-
-In this release, -cpu is an ignored argument.
-
PIC interrupts on Windows 10
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
On Windows 10, a legacy PIC interrupt injected does not wake the guest
from an HLT when using the Hyper-V provided interrupt controller.
-This has been addressed in QEMU 11.0 on Windows 11 platforms but
-functionality to make it available on Windows 10 isn't present.
+As such, on Windows 10, using the Hyper-V interrupt controller is
+disabled by default. You can enable it via ``-M q35,pic=off`` which
+disables the PIC. In that configuration, using a UEFI is recommended.
-Workaround: for affected use cases, use ``-M kernel-irqchip=off``.
+On this release, ``-M kernel-irqchip=`` is not expected to be manually
+set during normal operation. It remains as a debugging option.
Known issues on Windows 11
^^^^^^^^^^^^^^^^^^^^^^^^^^
-Nested virtualisation-specific Hyper-V enlightenments are not
-currently exposed.
-
arm64
-----
--
2.54.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* Re: [PULL 00/58] Misc patches for 2026-04-30
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
` (56 preceding siblings ...)
2026-04-30 17:22 ` [PULL 58/58] whpx: i386: documentation update Paolo Bonzini
@ 2026-05-02 10:49 ` Stefan Hajnoczi
57 siblings, 0 replies; 63+ messages in thread
From: Stefan Hajnoczi @ 2026-05-02 10:49 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 116 bytes --]
Applied, thanks.
Please update the changelog at https://wiki.qemu.org/ChangeLog/11.1 for any user-visible changes.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA
2026-04-30 17:21 ` [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA Paolo Bonzini
@ 2026-05-04 6:28 ` Philippe Mathieu-Daudé
2026-05-04 7:08 ` Paolo Bonzini
0 siblings, 1 reply; 63+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-05-04 6:28 UTC (permalink / raw)
To: Paolo Bonzini, qemu-devel; +Cc: Marc-André Lureau, Alistair Francis
On 30/4/26 19:21, Paolo Bonzini wrote:
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
This was not reviewed by Marc-André (at least not publically on
the list) but by Alistair and myself:
https://lore.kernel.org/qemu-devel/20260430090534.841894-3-pbonzini@redhat.com/
Not a big deal, it just shows this email workflow limits, as
reviewers might wonder what the point of doing public review if
being ignored.
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> hw/misc/Kconfig | 8 --------
> hw/riscv/Kconfig | 4 ++++
> 2 files changed, 4 insertions(+), 8 deletions(-)
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA
2026-05-04 6:28 ` Philippe Mathieu-Daudé
@ 2026-05-04 7:08 ` Paolo Bonzini
2026-05-04 7:36 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 63+ messages in thread
From: Paolo Bonzini @ 2026-05-04 7:08 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: qemu-devel, Marc-André Lureau, Alistair Francis
[-- Attachment #1: Type: text/plain, Size: 900 bytes --]
Il lun 4 mag 2026, 08:28 Philippe Mathieu-Daudé <philmd@linaro.org> ha
scritto:
> On 30/4/26 19:21, Paolo Bonzini wrote:
> > Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> This was not reviewed by Marc-André (at least not publically on
> the list) but by Alistair and myself:
>
> https://lore.kernel.org/qemu-devel/20260430090534.841894-3-pbonzini@redhat.com/
He did for the whole series, and then I sent out the pull request (having
had reviews) before receiving the messages from you and Alistair.
Paolo
> Not a big deal, it just shows this email workflow limits, as
> reviewers might wonder what the point of doing public review if
> being ignored.
>
> > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> > ---
> > hw/misc/Kconfig | 8 --------
> > hw/riscv/Kconfig | 4 ++++
> > 2 files changed, 4 insertions(+), 8 deletions(-)
>
>
[-- Attachment #2: Type: text/html, Size: 1890 bytes --]
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA
2026-05-04 7:08 ` Paolo Bonzini
@ 2026-05-04 7:36 ` Philippe Mathieu-Daudé
2026-05-04 7:57 ` Paolo Bonzini
0 siblings, 1 reply; 63+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-05-04 7:36 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: qemu-devel, Marc-André Lureau, Alistair Francis
On 4/5/26 09:08, Paolo Bonzini wrote:
>
>
> Il lun 4 mag 2026, 08:28 Philippe Mathieu-Daudé <philmd@linaro.org
> <mailto:philmd@linaro.org>> ha scritto:
>
> On 30/4/26 19:21, Paolo Bonzini wrote:
> > Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com
> <mailto:marcandre.lureau@redhat.com>>
>
> This was not reviewed by Marc-André (at least not publically on
> the list) but by Alistair and myself:
> https://lore.kernel.org/qemu-devel/20260430090534.841894-3-
> pbonzini@redhat.com/ <https://lore.kernel.org/qemu-
> devel/20260430090534.841894-3-pbonzini@redhat.com/>
>
>
> He did for the whole series, and then I sent out the pull request
> (having had reviews) before receiving the messages from you and Alistair.
Indeed you are right, I missed whole-series review sent to the cover
letter. I suppose the other my other hiccup is due to emails delivery
delay, not seeing your PR was sent later the same day. Apologies.
>
> Paolo
>
>
> Not a big deal, it just shows this email workflow limits, as
> reviewers might wonder what the point of doing public review if
> being ignored.
>
> > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com
> <mailto:pbonzini@redhat.com>>
> > ---
> > hw/misc/Kconfig | 8 --------
> > hw/riscv/Kconfig | 4 ++++
> > 2 files changed, 4 insertions(+), 8 deletions(-)
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA
2026-05-04 7:36 ` Philippe Mathieu-Daudé
@ 2026-05-04 7:57 ` Paolo Bonzini
0 siblings, 0 replies; 63+ messages in thread
From: Paolo Bonzini @ 2026-05-04 7:57 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: qemu-devel, Marc-André Lureau, Alistair Francis
[-- Attachment #1: Type: text/plain, Size: 996 bytes --]
Il lun 4 mag 2026, 09:36 Philippe Mathieu-Daudé <philmd@linaro.org> ha
scritto:
> Indeed you are right, I missed whole-series review sent to the cover
> letter. I suppose the other my other hiccup is due to emails delivery
> delay, not seeing your PR was sent later the same day. Apologies.
>
No problem - it is not usual to send the PR the same day, which I did only
because there are clearly no functional changes.
And I have been less diligent at times collecting R-b for my own patches,
so...
Paolo
>
> > Paolo
> >
> >
> > Not a big deal, it just shows this email workflow limits, as
> > reviewers might wonder what the point of doing public review if
> > being ignored.
> >
> > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com
> > <mailto:pbonzini@redhat.com>>
> > > ---
> > > hw/misc/Kconfig | 8 --------
> > > hw/riscv/Kconfig | 4 ++++
> > > 2 files changed, 4 insertions(+), 8 deletions(-)
> >
>
>
[-- Attachment #2: Type: text/html, Size: 1969 bytes --]
^ permalink raw reply [flat|nested] 63+ messages in thread
end of thread, other threads:[~2026-05-04 12:44 UTC | newest]
Thread overview: 63+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-30 17:21 [PULL 00/58] Misc patches for 2026-04-30 Paolo Bonzini
2026-04-30 17:21 ` [PULL 02/58] target/arm/hvf, target/i386/hvf: Pass MR-relative offset to memory_region_set_dirty() Paolo Bonzini
2026-04-30 17:21 ` [PULL 03/58] target/i386/tcg: simplify decoding of 0F 38 F0...FF Paolo Bonzini
2026-04-30 17:21 ` [PULL 04/58] tests: add test for json-streamer.c error recovery Paolo Bonzini
2026-04-30 17:21 ` [PULL 05/58] kconfig: remove duplicate declaration of CONFIG_CXL Paolo Bonzini
2026-04-30 17:21 ` [PULL 06/58] kconfig: remove duplicate declaration of CONFIG_MIPS_BOSTON_AIA Paolo Bonzini
2026-05-04 6:28 ` Philippe Mathieu-Daudé
2026-05-04 7:08 ` Paolo Bonzini
2026-05-04 7:36 ` Philippe Mathieu-Daudé
2026-05-04 7:57 ` Paolo Bonzini
2026-04-30 17:21 ` [PULL 07/58] minikconf: run through isort Paolo Bonzini
2026-04-30 17:21 ` [PULL 08/58] minikconf: small cleanups and dead code removal Paolo Bonzini
2026-04-30 17:21 ` [PULL 09/58] minikconf: move command-line assignment out of the parser Paolo Bonzini
2026-04-30 17:21 ` [PULL 10/58] minikconf: fix type mismatch in do_declaration Paolo Bonzini
2026-04-30 17:21 ` [PULL 11/58] minikconf: simplify self.tok Paolo Bonzini
2026-04-30 17:21 ` [PULL 12/58] minikconf: modernize handling of include chain Paolo Bonzini
2026-04-30 17:21 ` [PULL 13/58] minikconf: use .items() Paolo Bonzini
2026-04-30 17:21 ` [PULL 14/58] minikconf: pull main program into a function Paolo Bonzini
2026-04-30 17:21 ` [PULL 15/58] minikconf: remove unnecessary semicolons Paolo Bonzini
2026-04-30 17:21 ` [PULL 16/58] minikconf: replace else with early return and avoid unnecessary else Paolo Bonzini
2026-04-30 17:21 ` [PULL 17/58] minikconf: add mypy annotations Paolo Bonzini
2026-04-30 17:21 ` [PULL 18/58] hw/qdev: Clarify fallback order in qdev_get_printable_name() Paolo Bonzini
2026-04-30 17:21 ` [PULL 19/58] hw/qdev: Prefix bus type in qdev_get_printable_name() device paths Paolo Bonzini
2026-04-30 17:21 ` [PULL 20/58] hw/qdev: Consolidate qdev_get_printable_name() into qdev_get_human_name() Paolo Bonzini
2026-04-30 17:21 ` [PULL 21/58] target/i386: add new AMD EPYC models for GMET enablement Paolo Bonzini
2026-04-30 17:21 ` [PULL 22/58] target/i386: add new Intel models for MMIO/GDS/RFDS mitigation status Paolo Bonzini
2026-04-30 17:21 ` [PULL 23/58] target/i386: add new Intel models for MBEC enablement Paolo Bonzini
2026-04-30 17:21 ` [PULL 24/58] whpx: i386: x2apic emulation Paolo Bonzini
2026-04-30 17:21 ` [PULL 25/58] whpx: i386: wire up feature probing Paolo Bonzini
2026-04-30 17:21 ` [PULL 26/58] whpx: i386: disable TbFlushHypercalls for emulated LAPIC Paolo Bonzini
2026-04-30 17:21 ` [PULL 27/58] whpx: i386: enable x2apic by default for user-mode LAPIC Paolo Bonzini
2026-04-30 17:21 ` [PULL 28/58] whpx: i386: reintroduce enlightenments for Windows 10 Paolo Bonzini
2026-04-30 17:21 ` [PULL 29/58] whpx: i386: introduce proper cpuid support Paolo Bonzini
2026-04-30 17:21 ` [PULL 30/58] whpx: i386: kernel-irqchip=off fixes Paolo Bonzini
2026-04-30 17:21 ` [PULL 31/58] whpx: i386: use WHvX64RegisterCr8 only when kernel-irqchip=off Paolo Bonzini
2026-04-30 17:21 ` [PULL 32/58] whpx: i386: disable kernel-irqchip on Windows 10 when PIC enabled Paolo Bonzini
2026-04-30 17:21 ` [PULL 33/58] whpx: i386: IO port fast path cleanup Paolo Bonzini
2026-04-30 17:21 ` [PULL 34/58] whpx: i386: disable enlightenments and LAPIC for isapc Paolo Bonzini
2026-04-30 17:21 ` [PULL 35/58] whpx: i386: interrupt priority support Paolo Bonzini
2026-04-30 17:21 ` [PULL 36/58] hw/intc: apic: disallow APIC reads when disabled Paolo Bonzini
2026-04-30 17:21 ` [PULL 37/58] whpx: i386: fix CPUID[1:EDX].APIC reporting Paolo Bonzini
2026-04-30 17:21 ` [PULL 38/58] whpx: i386: set apicbase value only on success Paolo Bonzini
2026-04-30 17:21 ` [PULL 39/58] whpx: i386: enable GuestIdleReg enlightenment Paolo Bonzini
2026-04-30 17:21 ` [PULL 40/58] whpx: i386: unknown MSR configurability Paolo Bonzini
2026-04-30 17:21 ` [PULL 41/58] whpx: i386: don't increment eip on MSR access raising GPF Paolo Bonzini
2026-04-30 17:21 ` [PULL 42/58] target/i386: emulate, hvf: rdmsr/wrmsr GPF handling Paolo Bonzini
2026-04-30 17:21 ` [PULL 43/58] whpx: i386: tighten APIC base validity check Paolo Bonzini
2026-04-30 17:21 ` [PULL 44/58] whpx: i386: ignore vpassist when kernel-irqchip=off Paolo Bonzini
2026-04-30 17:21 ` [PULL 45/58] target: i386: HLT type that ignores EFLAGS.IF Paolo Bonzini
2026-04-30 17:21 ` [PULL 46/58] whpx: i386: add HV_X64_MSR_GUEST_IDLE when !kernel-irqchip Paolo Bonzini
2026-04-30 17:21 ` [PULL 47/58] whpx: i386: some x2APIC awareness Paolo Bonzini
2026-04-30 17:21 ` [PULL 48/58] whpx: i386: set WHvX64RegisterInitialApicId Paolo Bonzini
2026-04-30 17:21 ` [PULL 49/58] whpx: i386: Pause VM on fatal exception to be able to inspect state Paolo Bonzini
2026-04-30 17:21 ` [PULL 50/58] target/i386: emulate: use exception_payload for fault address Paolo Bonzini
2026-04-30 17:21 ` [PULL 51/58] target/i386: make xsave_buf present unconditionally Paolo Bonzini
2026-04-30 17:21 ` [PULL 52/58] target/i386: add de/compaction to xsave_helper Paolo Bonzini
2026-04-30 17:21 ` [PULL 53/58] whpx: xsave support Paolo Bonzini
2026-04-30 17:22 ` [PULL 54/58] whpx: i386: set APIC ID only when APIC present Paolo Bonzini
2026-04-30 17:22 ` [PULL 55/58] whpx: i386: update migration blocker message Paolo Bonzini
2026-04-30 17:22 ` [PULL 56/58] whpx: i386: add feature to intercept #GP MSR accesses Paolo Bonzini
2026-04-30 17:22 ` [PULL 57/58] whpx: i386: add SeparateSecurityDomain flag and make default Paolo Bonzini
2026-04-30 17:22 ` [PULL 58/58] whpx: i386: documentation update Paolo Bonzini
2026-05-02 10:49 ` [PULL 00/58] Misc patches for 2026-04-30 Stefan Hajnoczi
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.