From 1feb92de3414c192a6cbf510679209c8b71a7a6d Mon Sep 17 00:00:00 2001 From: Mohamed Mediouni Date: Fri, 27 Mar 2026 02:11:49 +0100 Subject: [PATCH 01/12] meson.build: remove i386-softmmu WHPX support target/i386/emulate is pretty much incompatible with i386-softmmu and fixing that doesn't look worthwhile given the binary unification goals... Signed-off-by: Mohamed Mediouni Link: https://lore.kernel.org/r/20260327011152.4126-2-mohamed@unpredictable.fr Signed-off-by: Paolo Bonzini --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index d7c4095b39..407eef5b4e 100644 --- a/meson.build +++ b/meson.build @@ -314,7 +314,7 @@ elif cpu == 'x86_64' 'CONFIG_HVF': ['x86_64-softmmu'], 'CONFIG_NITRO': ['x86_64-softmmu'], 'CONFIG_NVMM': ['i386-softmmu', 'x86_64-softmmu'], - 'CONFIG_WHPX': ['i386-softmmu', 'x86_64-softmmu'], + 'CONFIG_WHPX': ['x86_64-softmmu'], 'CONFIG_MSHV': ['x86_64-softmmu'], } endif From ea1169bca45b45764753ba9122c34f5a1157bb09 Mon Sep 17 00:00:00 2001 From: Mohamed Mediouni Date: Fri, 27 Mar 2026 02:11:50 +0100 Subject: [PATCH 02/12] docs: add WHPX section with initial info Signed-off-by: Mohamed Mediouni Link: https://lore.kernel.org/r/20260327011152.4126-3-mohamed@unpredictable.fr Signed-off-by: Paolo Bonzini --- MAINTAINERS | 1 + docs/system/index.rst | 1 + docs/system/whpx.rst | 144 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 docs/system/whpx.rst diff --git a/MAINTAINERS b/MAINTAINERS index cd8ba14450..eb7132e39d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -582,6 +582,7 @@ F: include/system/whpx.h F: include/system/whpx-accel-ops.h F: include/system/whpx-common.h F: include/system/whpx-internal.h +F: docs/system/whpx.rst MSHV M: Magnus Kulke diff --git a/docs/system/index.rst b/docs/system/index.rst index bb948e2993..4509630fa4 100644 --- a/docs/system/index.rst +++ b/docs/system/index.rst @@ -40,6 +40,7 @@ or Hypervisor.Framework. confidential-guest-support igvm nitro + whpx vm-templating sriov qemu-colo diff --git a/docs/system/whpx.rst b/docs/system/whpx.rst new file mode 100644 index 0000000000..3e1979028c --- /dev/null +++ b/docs/system/whpx.rst @@ -0,0 +1,144 @@ +Windows Hypervisor Platform +=========================== + +Windows Hypervisor Platform is the Windows API for use of +third-party virtual machine monitors with hardware acceleration +on Hyper-V. + +It's implemented on top of ``Vid``, which is itself implemented +on the same set of hypercalls as the ``mshv`` driver on Linux. + +WHPX is the name of the Windows Hypervisor Platform accelerator +backend in QEMU. It enables using QEMU with hardware acceleration +on both x86_64 and arm64 Windows machines. + +Prerequisites +------------- + +WHPX requires the Windows Hypervisor Platform feature to be installed. + +Installation +^^^^^^^^^^^^ +On client editions of Windows, that means installation through +Windows Features (``optionalfeatures.exe``). On server editions, +feature-based installation in Server Manager can be used. + +Alternatively, command line installation is also possible through: +``DISM /online /Enable-Feature /FeatureName:HypervisorPlatform /All`` + +Minimum OS version +^^^^^^^^^^^^^^^^^^ + +On x86_64, QEMU's Windows Hypervisor Platform backend is tested +starting from Windows 10 version 2004. Earlier Windows 10 releases +*might* work but are not tested. + +On arm64, Windows 11 24H2 with the April 2025 optional updates +or May 2025 security updates is the minimum required release. + +Prior releases of Windows 11 version 24H2 on ARM64 shipped +with a pre-release version of the Windows Hypervisor Platform +API, which is not supported in QEMU. + +Quick Start +----------- + +Launching a virtual machine on x86_64 with WHPX acceleration:: + + $ qemu-system-x86_64.exe -accel whpx -M pc \ + -smp cores=2 -m 2G -device ich9-usb-ehci1 \ + -device usb-tablet -hda OS.qcow2 + +Launching a virtual machine on arm64 with WHPX acceleration:: + + $ qemu-system-aarch64.exe -accel whpx -M virt \ + -cpu host -smp cores=2 -m 2G \ + -bios edk2-aarch64-code.fd \ + -device ramfb -device nec-usb-xhci \ + -device usb-kbd -device usb-tablet \ + -hda OS.qcow2 + +On arm64, for non-Windows guests, ``-device virtio-gpu-pci`` provides +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 +-------------------- + +x86_64 +^^^^^^ + +``-trace whpx_unsupported_msr_access`` can be used to log accesses +to undocumented MSRs. + +``-d invalid_mem`` allows to trace accesses to unmapped +GPAs. + +Known issues on x86_64 +---------------------- + +Guests using legacy VGA modes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In guests using VGA modes that QEMU doesn't pass through framebuffer +memory for, performance will be quite suboptimal. + +Workaround: for affected guests, use a more modern graphics mode. +Alternatively, use TCG to run those guests. + +Guests using MMX, SSE or AVX instructions for MMIO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Currently, ``target/i386/emulate`` does not support guests that use +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. + +Workaround: for affected use cases, use ``-M kernel-irqchip=off``. + +Known issues on Windows 11 +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Nested virtualisation-specific Hyper-V enlightenments are not +currently exposed. + +arm64 +----- + +ISA feature support +^^^^^^^^^^^^^^^^^^^ + +SVE and SME are not currently supported. From 77a5deba0d5ae7202c5b17cff278317538d28276 Mon Sep 17 00:00:00 2001 From: Mohamed Mediouni Date: Fri, 27 Mar 2026 02:11:51 +0100 Subject: [PATCH 03/12] whpx: i386: trace unsupported MSR accesses Not actionable information for users, so stop having it displayed unconditionally. Signed-off-by: Mohamed Mediouni Reviewed-by: Pierrick Bouvier Link: https://lore.kernel.org/r/20260327011152.4126-4-mohamed@unpredictable.fr Signed-off-by: Paolo Bonzini --- meson.build | 1 + target/i386/whpx/trace-events | 1 + target/i386/whpx/trace.h | 2 ++ target/i386/whpx/whpx-all.c | 5 +++-- 4 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 target/i386/whpx/trace-events create mode 100644 target/i386/whpx/trace.h diff --git a/meson.build b/meson.build index 407eef5b4e..daa58e46a3 100644 --- a/meson.build +++ b/meson.build @@ -3687,6 +3687,7 @@ if have_system or have_user 'target/hppa', 'target/i386', 'target/i386/kvm', + 'target/i386/whpx', 'target/loongarch', 'target/mips/tcg', 'target/ppc', diff --git a/target/i386/whpx/trace-events b/target/i386/whpx/trace-events new file mode 100644 index 0000000000..ebdfa34b28 --- /dev/null +++ b/target/i386/whpx/trace-events @@ -0,0 +1 @@ +whpx_unsupported_msr_access(uint32_t msr, int is_write) "WHPX: Unsupported MSR access (0x%x), IsWrite=%i" diff --git a/target/i386/whpx/trace.h b/target/i386/whpx/trace.h new file mode 100644 index 0000000000..b7c090deff --- /dev/null +++ b/target/i386/whpx/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-target_i386_whpx.h" diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c index acae61e089..e56ae2b343 100644 --- a/target/i386/whpx/whpx-all.c +++ b/target/i386/whpx/whpx-all.c @@ -41,6 +41,7 @@ #include "emulate/x86_emu.h" #include "emulate/x86_flags.h" #include "emulate/x86_mmu.h" +#include "trace.h" #include @@ -1921,8 +1922,8 @@ int whpx_vcpu_run(CPUState *cpu) 1 : 3; if (!is_known_msr) { - warn_report("WHPX: Unsupported MSR access (0x%x), IsWrite=%i", - vcpu->exit_ctx.MsrAccess.MsrNumber, vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite); + trace_whpx_unsupported_msr_access(vcpu->exit_ctx.MsrAccess.MsrNumber, + vcpu->exit_ctx.MsrAccess.AccessInfo.IsWrite); } hr = whp_dispatch.WHvSetVirtualProcessorRegisters( From ee5b216c0eb368d20114c6ff696ad637dbfb98c2 Mon Sep 17 00:00:00 2001 From: Mohamed Mediouni Date: Fri, 27 Mar 2026 02:11:52 +0100 Subject: [PATCH 04/12] target/i386: emulate: remove redundant logging for unmapped MMIO access ReactOS's install ISO does a bunch of 4-byte accesses to 0xffdff124. This doesn't happen for the boot ISO. It looks to be an access relative to the Windows KPCR which is at 0xffdff000 but mistakenly done prior to paging being on... As this logging is redundant with -d invalid_mem, remove it. https://geoffchappell.com/studies/windows/km/ntoskrnl/inc/ntos/i386_x/kpcr.htm Signed-off-by: Mohamed Mediouni Link: https://lore.kernel.org/r/20260327011152.4126-5-mohamed@unpredictable.fr Signed-off-by: Paolo Bonzini --- target/i386/emulate/x86_mmu.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/target/i386/emulate/x86_mmu.c b/target/i386/emulate/x86_mmu.c index c69ae96acb..007de582de 100644 --- a/target/i386/emulate/x86_mmu.c +++ b/target/i386/emulate/x86_mmu.c @@ -21,7 +21,6 @@ #include "cpu.h" #include "system/address-spaces.h" #include "system/memory.h" -#include "qemu/error-report.h" #include "emulate/x86.h" #include "emulate/x86_emu.h" #include "emulate/x86_mmu.h" @@ -287,7 +286,6 @@ static MMUTranslateResult x86_write_mem_ex(CPUState *cpu, void *data, target_ulo MEMTXATTRS_UNSPECIFIED, data, copy); if (mem_tx_res == MEMTX_DECODE_ERROR) { - warn_report("write to unmapped mmio region gpa=0x%" PRIx64 " size=%i", gpa, bytes); return MMU_TRANSLATE_GPA_UNMAPPED; } else if (mem_tx_res == MEMTX_ACCESS_ERROR) { return MMU_TRANSLATE_GPA_NO_WRITE_ACCESS; @@ -339,7 +337,6 @@ static MMUTranslateResult x86_read_mem_ex(CPUState *cpu, void *data, target_ulon data, copy); if (mem_tx_res == MEMTX_DECODE_ERROR) { - warn_report("read from unmapped mmio region gpa=0x%" PRIx64 " size=%i", gpa, bytes); return MMU_TRANSLATE_GPA_UNMAPPED; } else if (mem_tx_res == MEMTX_ACCESS_ERROR) { return MMU_TRANSLATE_GPA_NO_READ_ACCESS; From 24b9ebd5f1a5197779594b62c3d222e320f18447 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Mar 2026 17:25:06 +0100 Subject: [PATCH 05/12] hpet: fix bounds check for s->timer[] Fix an off-by-one issue in QEMU's HPET read and write MMIO handlers. Both handlers check timer_id > s->num_timers instead of timer_id >= s->num_timers, allowing a guest to access one timer beyond the valid range. The affected slot is initialized properly in hpet_realize, which goes through all HPET_MAX_TIMERS elements of the array, so even though it is not reset in hpet_reset() the bug does not cause any use of uninitialized host memory. Because of this, and also because (even though HPET_MAX_TIMERS is 32) the HPET only has room for 24 timers in its MMIO region, the bug has no security implications. Commit 869b0afa4fa ("rust/hpet: Drop BqlCell wrapper for num_timers", 2025-06-06) silently fixed the same bug in rust/hw/timer/hpet/src/device.rs. Reported-by: Yuma Kurogome, Ricerca Security, Inc. Signed-off-by: Paolo Bonzini --- hw/timer/hpet.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/hw/timer/hpet.c b/hw/timer/hpet.c index 767093c431..42285cff76 100644 --- a/hw/timer/hpet.c +++ b/hw/timer/hpet.c @@ -464,13 +464,14 @@ static uint64_t hpet_ram_read(void *opaque, hwaddr addr, } } else { uint8_t timer_id = (addr - 0x100) / 0x20; - HPETTimer *timer = &s->timer[timer_id]; + HPETTimer *timer; - if (timer_id > s->num_timers) { + if (timer_id >= s->num_timers) { trace_hpet_timer_id_out_of_range(timer_id); return 0; } + timer = &s->timer[timer_id]; switch (addr & 0x1f) { case HPET_TN_CFG: // including interrupt capabilities return timer->config >> shift; @@ -564,13 +565,15 @@ static void hpet_ram_write(void *opaque, hwaddr addr, } } else { uint8_t timer_id = (addr - 0x100) / 0x20; - HPETTimer *timer = &s->timer[timer_id]; + HPETTimer *timer; trace_hpet_ram_write_timer_id(timer_id); - if (timer_id > s->num_timers) { + if (timer_id >= s->num_timers) { trace_hpet_timer_id_out_of_range(timer_id); return; } + + timer = &s->timer[timer_id]; switch (addr & 0x18) { case HPET_TN_CFG: trace_hpet_ram_write_tn_cfg(addr & 4); From 7be175e65f07bdf93f5f576d1dbb5f993c97cd5c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Mar 2026 21:46:54 +0100 Subject: [PATCH 06/12] hpet: lower HPET_MAX_TIMERS to 24 Each timer block occupies 32 bytes, but they only start at offset 256 of the 1024-byte MMIO register space. Therefore the correct limit for HPET_MAX_TIMERS is 24, not 32. Signed-off-by: Paolo Bonzini --- include/hw/timer/hpet.h | 2 +- rust/hw/timer/hpet/src/device.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/hw/timer/hpet.h b/include/hw/timer/hpet.h index c2656f7f0b..84be4c3529 100644 --- a/include/hw/timer/hpet.h +++ b/include/hw/timer/hpet.h @@ -22,7 +22,7 @@ #define FS_PER_NS 1000000 /* 1000000 femtoseconds == 1 ns */ #define HPET_MIN_TIMERS 3 -#define HPET_MAX_TIMERS 32 +#define HPET_MAX_TIMERS 24 #define HPET_NUM_IRQ_ROUTES 32 diff --git a/rust/hw/timer/hpet/src/device.rs b/rust/hw/timer/hpet/src/device.rs index 0a5c131819..ec0bca4496 100644 --- a/rust/hw/timer/hpet/src/device.rs +++ b/rust/hw/timer/hpet/src/device.rs @@ -32,7 +32,7 @@ const HPET_REG_SPACE_LEN: u64 = 0x400; // 1024 bytes /// Minimum recommended hardware implementation. const HPET_MIN_TIMERS: usize = 3; /// Maximum timers in each timer block. -const HPET_MAX_TIMERS: usize = 32; +const HPET_MAX_TIMERS: usize = 24; /// Flags that HPETState.flags supports. const HPET_FLAG_MSI_SUPPORT_SHIFT: usize = 0; From 4862d2c95104d9fd0430cc003c205094f8ada1f9 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Mar 2026 17:37:31 +0100 Subject: [PATCH 07/12] lsi53c895a: keep a reference to the device while SCRIPTS execute SCRIPTS execution can trigger PCI device unplug and consequently a use-after-free after the unplug returns. Avoid this by keeping the device alive. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3090 Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini --- hw/scsi/lsi53c895a.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c index 6f43e500b3..90643b26ab 100644 --- a/hw/scsi/lsi53c895a.c +++ b/hw/scsi/lsi53c895a.c @@ -1163,6 +1163,7 @@ static void lsi_execute_script(LSIState *s) s->waiting = LSI_NOWAIT; } + object_ref(s); reentrancy_level++; s->istat1 |= LSI_ISTAT1_SRUN; @@ -1182,6 +1183,7 @@ again: s->waiting = LSI_WAIT_SCRIPTS; lsi_scripts_timer_start(s); reentrancy_level--; + object_unref(s); return; } insn = read_dword(s, s->dsp); @@ -1630,6 +1632,7 @@ again: trace_lsi_execute_script_stop(); reentrancy_level--; + object_unref(s); } static uint8_t lsi_reg_readb(LSIState *s, int offset) From 64807c84e83f767c135aa9ba4b5f61162bb177ef Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Mar 2026 17:39:15 +0100 Subject: [PATCH 08/12] lsi53c895a: do not do anything else if a reset is requested by writing ISTAT0 If the device is reset, anything that is done before will not really be visible. So do the reset and exit immediately if that is one of the requests in the value written to ISTAT0. Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini --- hw/scsi/lsi53c895a.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c index 90643b26ab..81b4f93f4d 100644 --- a/hw/scsi/lsi53c895a.c +++ b/hw/scsi/lsi53c895a.c @@ -1949,6 +1949,10 @@ static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val) CASE_SET_REG32(dsa, 0x10) case 0x14: /* ISTAT0 */ s->istat0 = (s->istat0 & 0x0f) | (val & 0xf0); + if (val & LSI_ISTAT0_SRST) { + device_cold_reset(DEVICE(s)); + return; + } if (val & LSI_ISTAT0_ABRT) { lsi_script_dma_interrupt(s, LSI_DSTAT_ABRT); } @@ -1962,9 +1966,6 @@ static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val) s->dsp = s->dnad; lsi_execute_script(s); } - if (val & LSI_ISTAT0_SRST) { - device_cold_reset(DEVICE(s)); - } break; case 0x16: /* MBOX0 */ s->mbox0 = val; From 1ca38f84e19427c462f077390492f971f9eb11eb Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Mar 2026 18:32:17 +0100 Subject: [PATCH 09/12] lsi53c895a: keep lsi_request and SCSIRequest in local variables Protect against changes from reentrant device MMIO during DMA, by always operating on the same request. Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini --- hw/scsi/lsi53c895a.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c index 81b4f93f4d..1180e60181 100644 --- a/hw/scsi/lsi53c895a.c +++ b/hw/scsi/lsi53c895a.c @@ -626,6 +626,8 @@ static void lsi_do_dma(LSIState *s, int out) uint32_t count; dma_addr_t addr; SCSIDevice *dev; + SCSIRequest *req; + lsi_request *p; if (!s->current || !s->current->dma_len) { /* Wait until data is available. */ @@ -633,12 +635,14 @@ static void lsi_do_dma(LSIState *s, int out) return; } - dev = s->current->req->dev; + p = s->current; + req = s->current->req; + dev = req->dev; assert(dev); count = s->dbc; - if (count > s->current->dma_len) - count = s->current->dma_len; + if (count > p->dma_len) + count = p->dma_len; addr = s->dnad; /* both 40 and Table Indirect 64-bit DMAs store upper bits in dnad64 */ @@ -653,21 +657,22 @@ static void lsi_do_dma(LSIState *s, int out) s->csbc += count; s->dnad += count; s->dbc -= count; - if (s->current->dma_buf == NULL) { - s->current->dma_buf = scsi_req_get_buf(s->current->req); + if (p->dma_buf == NULL) { + p->dma_buf = scsi_req_get_buf(req); } /* ??? Set SFBR to first data byte. */ if (out) { - lsi_mem_read(s, addr, s->current->dma_buf, count); + lsi_mem_read(s, addr, p->dma_buf, count); } else { - lsi_mem_write(s, addr, s->current->dma_buf, count); + lsi_mem_write(s, addr, p->dma_buf, count); } - s->current->dma_len -= count; - if (s->current->dma_len == 0) { - s->current->dma_buf = NULL; - scsi_req_continue(s->current->req); + + p->dma_len -= count; + if (p->dma_len == 0) { + p->dma_buf = NULL; + scsi_req_continue(req); } else { - s->current->dma_buf += count; + p->dma_buf += count; lsi_resume_script(s); } } From 7c7aaaa342b57b0099d7fc4a9803e987b891322b Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Mar 2026 18:40:45 +0100 Subject: [PATCH 10/12] lsi53c895a: keep lsi_request alive as long as the SCSIRequest To protect against using the lsi_request after SCSIRequest has been freed, keep the HBA-private data alive until the last reference to the SCSIRequest is gone. Because req->hba_private was used (even if just for an assertion) to check that the request was still either current or queued, add a boolean field that is set when the SCSIRequest is cancelled or completed, which is when the lsi_request would have been unqueued. Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini --- hw/scsi/lsi53c895a.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c index 1180e60181..b882fc0227 100644 --- a/hw/scsi/lsi53c895a.c +++ b/hw/scsi/lsi53c895a.c @@ -197,6 +197,7 @@ typedef struct lsi_request { uint8_t *dma_buf; uint32_t pending; int out; + bool orphan; QTAILQ_ENTRY(lsi_request) next; } lsi_request; @@ -748,14 +749,20 @@ static lsi_request *lsi_find_by_tag(LSIState *s, uint32_t tag) return NULL; } -static void lsi_request_free(LSIState *s, lsi_request *p) +static void lsi_request_orphan(LSIState *s, lsi_request *p) { + p->orphan = true; if (p == s->current) { s->current = NULL; } else { QTAILQ_REMOVE(&s->queue, p, next); } - g_free(p); + scsi_req_unref(p->req); +} + +static void lsi_free_request(SCSIBus *bus, void *priv) +{ + g_free(priv); } static void lsi_request_cancelled(SCSIRequest *req) @@ -763,9 +770,7 @@ static void lsi_request_cancelled(SCSIRequest *req) LSIState *s = LSI53C895A(req->bus->qbus.parent); lsi_request *p = req->hba_private; - req->hba_private = NULL; - lsi_request_free(s, p); - scsi_req_unref(req); + lsi_request_orphan(s, p); } /* Record that data is available for a queued command. Returns zero if @@ -817,9 +822,7 @@ static void lsi_command_complete(SCSIRequest *req, size_t resid) } if (req->hba_private == s->current) { - req->hba_private = NULL; - lsi_request_free(s, s->current); - scsi_req_unref(req); + lsi_request_orphan(s, s->current); } if (!stop) { lsi_resume_script(s); @@ -830,10 +833,11 @@ static void lsi_command_complete(SCSIRequest *req, size_t resid) static void lsi_transfer_data(SCSIRequest *req, uint32_t len) { LSIState *s = LSI53C895A(req->bus->qbus.parent); + lsi_request *p = req->hba_private; int out; - assert(req->hba_private); - if (s->waiting == LSI_WAIT_RESELECT || req->hba_private != s->current || + assert(!p->orphan); + if (s->waiting == LSI_WAIT_RESELECT || p != s->current || (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON))) { if (lsi_queue_req(s, req, len)) { return; @@ -2325,7 +2329,8 @@ static const struct SCSIBusInfo lsi_scsi_info = { .transfer_data = lsi_transfer_data, .complete = lsi_command_complete, - .cancel = lsi_request_cancelled + .cancel = lsi_request_cancelled, + .free_request = lsi_free_request, }; static void scripts_timer_cb(void *opaque) From d459131ff590c517bc89fa5867d4878b5eacbc30 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 27 Mar 2026 18:42:45 +0100 Subject: [PATCH 11/12] lsi53c895a: keep SCSIRequest alive during DMA Reentrant MMIO can cause the SCSIRequest to be completed, at which point lsi_request_orphan would drop the last reference. Anything that happens afterwards would access freed data. Keep a reference to the SCSIRequest and, through req->hba_private, to the lsi_request* for as long as DMA runs. Reported-by: Jihe Wang Cc: qemu-stable@nongnu.org Signed-off-by: Paolo Bonzini --- hw/scsi/lsi53c895a.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c index b882fc0227..54123f7757 100644 --- a/hw/scsi/lsi53c895a.c +++ b/hw/scsi/lsi53c895a.c @@ -637,7 +637,7 @@ static void lsi_do_dma(LSIState *s, int out) } p = s->current; - req = s->current->req; + req = scsi_req_ref(s->current->req); dev = req->dev; assert(dev); @@ -667,6 +667,11 @@ static void lsi_do_dma(LSIState *s, int out) } else { lsi_mem_write(s, addr, p->dma_buf, count); } + if (p->orphan) { + scsi_req_unref(req); + return; + } + scsi_req_unref(req); p->dma_len -= count; if (p->dma_len == 0) { From 0f254f70d4fcf668b27bd7c5970761afe13589c7 Mon Sep 17 00:00:00 2001 From: Zhao Liu Date: Mon, 30 Mar 2026 13:30:08 +0800 Subject: [PATCH 12/12] hw/acpi: Do not save/load cpuhp state unconditionally Commit 7aa563630b6b ("pc: Start with modern CPU hotplug interface by default") removed the .needed callback (vmstate_test_use_cpuhp) from vmstate_cpuhp_state in both piix4.c and ich9.c. However, PIIX4 is also used by non-PC boards - MIPS Malta, which does not select CONFIG_ACPI_CPU_HOTPLUG. For MIPS Malta, the linker resolves vmstate_cpu_hotplug to the stub one in acpi-cpu-hotplug-stub.c, which is a zero-initialized VMStateDescription with .fields == NULL. Before commit 7aa563630b6b, .needed() of PIIX4's vmstate_cpuhp_state returned false for MIPS Malta since PIIX4PMState always initialized the field cpu_hotplug_legacy as true. Malta implicitly relies on this initial value to bypass vmstate_cpuhp_state. However, this is unstable because Malta itself does not support CPU hotplugging, whether via the legacy way or the modern way. Commit 7aa563630b6b removed .needed() check for vmstate_cpuhp_state, this broke the existing dependency that Malta had relied on, forcing Malta to save and load vmstate_cpuhp_state during the save/load process, which in turn caused a segmentation fault due to NULL fields in the stub-compiled code. Fix this by bringing back the .needed = cpuhp_needed callback for vmstate_cpuhp_state of PIIX4, that checks MachineClass::has_hotpluggable_cpus. Boards that do not support CPU hotplug (only MIPS Malta) will skip this subsection entirely, which is both correct and consistent with the previous behavior. At the same time, add a similar .needed() check to ICH9. Although no boards with ICH9 are affected by this issue, this helps avoid potential issues in the future. Reproducer (MIPS Malta): $ qemu-img create -f qcow2 dummy.qcow2 32M $ qemu-system-mipsel -nographic \ -drive if=none,format=qcow2,file=dummy.qcow2 [Type "C-a c" to get the "(qemu)" monitor prompt)] (qemu) savevm foo # segfault Reported-by: Peter Maydell Fixes: 7aa563630b6b ("pc: Start with modern CPU hotplug interface by default") Signed-off-by: Zhao Liu Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3360 Tested-by: Peter Maydell Link: https://lore.kernel.org/r/20260330053008.2721532-1-zhao1.liu@intel.com Signed-off-by: Paolo Bonzini --- hw/acpi/ich9.c | 8 ++++++++ hw/acpi/piix4.c | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/hw/acpi/ich9.c b/hw/acpi/ich9.c index bbb1bd60a2..5c7dfb2c69 100644 --- a/hw/acpi/ich9.c +++ b/hw/acpi/ich9.c @@ -184,10 +184,18 @@ static const VMStateDescription vmstate_tco_io_state = { } }; +static bool cpuhp_needed(void *opaque) +{ + MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); + + return mc->has_hotpluggable_cpus; +} + static const VMStateDescription vmstate_cpuhp_state = { .name = "ich9_pm/cpuhp", .version_id = 1, .minimum_version_id = 1, + .needed = cpuhp_needed, .fields = (const VMStateField[]) { VMSTATE_CPU_HOTPLUG(cpuhp_state, ICH9LPCPMRegs), VMSTATE_END_OF_LIST() diff --git a/hw/acpi/piix4.c b/hw/acpi/piix4.c index 43860d1227..9b7f50c7af 100644 --- a/hw/acpi/piix4.c +++ b/hw/acpi/piix4.c @@ -195,10 +195,18 @@ static const VMStateDescription vmstate_memhp_state = { } }; +static bool cpuhp_needed(void *opaque) +{ + MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); + + return mc->has_hotpluggable_cpus; +} + static const VMStateDescription vmstate_cpuhp_state = { .name = "piix4_pm/cpuhp", .version_id = 1, .minimum_version_id = 1, + .needed = cpuhp_needed, .fields = (const VMStateField[]) { VMSTATE_CPU_HOTPLUG(cpuhp_state, PIIX4PMState), VMSTATE_END_OF_LIST()