Commit 0f254f70d4 for qemu.org

commit 0f254f70d4fcf668b27bd7c5970761afe13589c7
Author: Zhao Liu <zhao1.liu@intel.com>
Date:   Mon Mar 30 13:30:08 2026 +0800

    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 <peter.maydell@linaro.org>
    Fixes: 7aa563630b6b ("pc: Start with modern CPU hotplug interface by default")
    Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
    Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3360
    Tested-by: Peter Maydell <peter.maydell@linaro.org>
    Link: https://lore.kernel.org/r/20260330053008.2721532-1-zhao1.liu@intel.com
    Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

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()