virtio-mem: support suspend+resume
With virtio-mem, primarily hibernation is problematic: as the machine shuts down, the virtio-mem device loses its state. Powering the machine back up is like losing a bunch of DIMMs. While there would be ways to add limited support, suspend+resume is more commonly used for VMs and "easier" to support cleanly. s2idle can be supported without any device dependencies. Similarly, one would expect suspend-to-ram (i.e., S3) to work out of the box. However, QEMU currently unplugs all device memory when resuming the VM, using a cold reset on the "wakeup" path. In order to support S3, we need a feature flag for the device to tell us if memory remains plugged when waking up. In the future, QEMU will implement this feature. So let's always support s2idle and support S3 with plugged memory only if the device indicates support. Block hibernation early using the PM notifier. Trying to hibernate now fails early: # echo disk > /sys/power/state [ 26.455369] PM: hibernation: hibernation entry [ 26.458271] virtio_mem virtio0: hibernation is not supported. [ 26.462498] PM: hibernation: hibernation exit -bash: echo: write error: Operation not permitted s2idle works even without the new feature bit: # echo s2idle > /sys/power/mem_sleep # echo mem > /sys/power/state [ 52.083725] PM: suspend entry (s2idle) [ 52.095950] Filesystems sync: 0.010 seconds [ 52.101493] Freezing user space processes [ 52.104213] Freezing user space processes completed (elapsed 0.001 seconds) [ 52.106520] OOM killer disabled. [ 52.107655] Freezing remaining freezable tasks [ 52.110880] Freezing remaining freezable tasks completed (elapsed 0.001 seconds) [ 52.113296] printk: Suspending console(s) (use no_console_suspend to debug) S3 does not work without the feature bit when memory is plugged: # echo deep > /sys/power/mem_sleep # echo mem > /sys/power/state [ 32.788281] PM: suspend entry (deep) [ 32.816630] Filesystems sync: 0.027 seconds [ 32.820029] Freezing user space processes [ 32.823870] Freezing user space processes completed (elapsed 0.001 seconds) [ 32.827756] OOM killer disabled. [ 32.829608] Freezing remaining freezable tasks [ 32.833842] Freezing remaining freezable tasks completed (elapsed 0.001 seconds) [ 32.837953] printk: Suspending console(s) (use no_console_suspend to debug) [ 32.916172] virtio_mem virtio0: suspend+resume with plugged memory is not supported [ 32.916181] virtio-pci 0000:00:02.0: PM: pci_pm_suspend(): virtio_pci_freeze+0x0/0x50 returns -1 [ 32.916197] virtio-pci 0000:00:02.0: PM: dpm_run_callback(): pci_pm_suspend+0x0/0x170 returns -1 [ 32.916210] virtio-pci 0000:00:02.0: PM: failed to suspend async: error -1 But S3 works with the new feature bit when memory is plugged (patched QEMU): # echo deep > /sys/power/mem_sleep # echo mem > /sys/power/state [ 33.983694] PM: suspend entry (deep) [ 34.009828] Filesystems sync: 0.024 seconds [ 34.013589] Freezing user space processes [ 34.016722] Freezing user space processes completed (elapsed 0.001 seconds) [ 34.019092] OOM killer disabled. [ 34.020291] Freezing remaining freezable tasks [ 34.023549] Freezing remaining freezable tasks completed (elapsed 0.001 seconds) [ 34.026090] printk: Suspending console(s) (use no_console_suspend to debug) Cc: "Michael S. Tsirkin" <mst@redhat.com> Cc: Jason Wang <jasowang@redhat.com> Cc: Xuan Zhuo <xuanzhuo@linux.alibaba.com> Signed-off-by: David Hildenbrand <david@redhat.com> Message-Id: <20240318120645.105664-1-david@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
parent
240a1853b4
commit
e4544c550e
@ -21,6 +21,7 @@
|
|||||||
#include <linux/bitmap.h>
|
#include <linux/bitmap.h>
|
||||||
#include <linux/lockdep.h>
|
#include <linux/lockdep.h>
|
||||||
#include <linux/log2.h>
|
#include <linux/log2.h>
|
||||||
|
#include <linux/suspend.h>
|
||||||
|
|
||||||
#include <acpi/acpi_numa.h>
|
#include <acpi/acpi_numa.h>
|
||||||
|
|
||||||
@ -252,6 +253,9 @@ struct virtio_mem {
|
|||||||
/* Memory notifier (online/offline events). */
|
/* Memory notifier (online/offline events). */
|
||||||
struct notifier_block memory_notifier;
|
struct notifier_block memory_notifier;
|
||||||
|
|
||||||
|
/* Notifier to block hibernation image storing/reloading. */
|
||||||
|
struct notifier_block pm_notifier;
|
||||||
|
|
||||||
#ifdef CONFIG_PROC_VMCORE
|
#ifdef CONFIG_PROC_VMCORE
|
||||||
/* vmcore callback for /proc/vmcore handling in kdump mode */
|
/* vmcore callback for /proc/vmcore handling in kdump mode */
|
||||||
struct vmcore_cb vmcore_cb;
|
struct vmcore_cb vmcore_cb;
|
||||||
@ -1111,6 +1115,25 @@ static int virtio_mem_memory_notifier_cb(struct notifier_block *nb,
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int virtio_mem_pm_notifier_cb(struct notifier_block *nb,
|
||||||
|
unsigned long action, void *arg)
|
||||||
|
{
|
||||||
|
struct virtio_mem *vm = container_of(nb, struct virtio_mem,
|
||||||
|
pm_notifier);
|
||||||
|
switch (action) {
|
||||||
|
case PM_HIBERNATION_PREPARE:
|
||||||
|
case PM_RESTORE_PREPARE:
|
||||||
|
/*
|
||||||
|
* When restarting the VM, all memory is unplugged. Don't
|
||||||
|
* allow to hibernate and restore from an image.
|
||||||
|
*/
|
||||||
|
dev_err(&vm->vdev->dev, "hibernation is not supported.\n");
|
||||||
|
return NOTIFY_BAD;
|
||||||
|
default:
|
||||||
|
return NOTIFY_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set a range of pages PG_offline. Remember pages that were never onlined
|
* Set a range of pages PG_offline. Remember pages that were never onlined
|
||||||
* (via generic_online_page()) using PageDirty().
|
* (via generic_online_page()) using PageDirty().
|
||||||
@ -2615,11 +2638,19 @@ static int virtio_mem_init_hotplug(struct virtio_mem *vm)
|
|||||||
rc = register_memory_notifier(&vm->memory_notifier);
|
rc = register_memory_notifier(&vm->memory_notifier);
|
||||||
if (rc)
|
if (rc)
|
||||||
goto out_unreg_group;
|
goto out_unreg_group;
|
||||||
rc = register_virtio_mem_device(vm);
|
/* Block hibernation as early as possible. */
|
||||||
|
vm->pm_notifier.priority = INT_MAX;
|
||||||
|
vm->pm_notifier.notifier_call = virtio_mem_pm_notifier_cb;
|
||||||
|
rc = register_pm_notifier(&vm->pm_notifier);
|
||||||
if (rc)
|
if (rc)
|
||||||
goto out_unreg_mem;
|
goto out_unreg_mem;
|
||||||
|
rc = register_virtio_mem_device(vm);
|
||||||
|
if (rc)
|
||||||
|
goto out_unreg_pm;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
out_unreg_pm:
|
||||||
|
unregister_pm_notifier(&vm->pm_notifier);
|
||||||
out_unreg_mem:
|
out_unreg_mem:
|
||||||
unregister_memory_notifier(&vm->memory_notifier);
|
unregister_memory_notifier(&vm->memory_notifier);
|
||||||
out_unreg_group:
|
out_unreg_group:
|
||||||
@ -2897,6 +2928,7 @@ static void virtio_mem_deinit_hotplug(struct virtio_mem *vm)
|
|||||||
|
|
||||||
/* unregister callbacks */
|
/* unregister callbacks */
|
||||||
unregister_virtio_mem_device(vm);
|
unregister_virtio_mem_device(vm);
|
||||||
|
unregister_pm_notifier(&vm->pm_notifier);
|
||||||
unregister_memory_notifier(&vm->memory_notifier);
|
unregister_memory_notifier(&vm->memory_notifier);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2960,17 +2992,40 @@ static void virtio_mem_config_changed(struct virtio_device *vdev)
|
|||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
static int virtio_mem_freeze(struct virtio_device *vdev)
|
static int virtio_mem_freeze(struct virtio_device *vdev)
|
||||||
{
|
{
|
||||||
|
struct virtio_mem *vm = vdev->priv;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When restarting the VM, all memory is usually unplugged. Don't
|
* We block hibernation using the PM notifier completely. The workqueue
|
||||||
* allow to suspend/hibernate.
|
* is already frozen by the PM core at this point, so we simply
|
||||||
|
* reset the device and cleanup the queues.
|
||||||
*/
|
*/
|
||||||
dev_err(&vdev->dev, "save/restore not supported.\n");
|
if (pm_suspend_target_state != PM_SUSPEND_TO_IDLE &&
|
||||||
return -EPERM;
|
vm->plugged_size &&
|
||||||
|
!virtio_has_feature(vm->vdev, VIRTIO_MEM_F_PERSISTENT_SUSPEND)) {
|
||||||
|
dev_err(&vm->vdev->dev,
|
||||||
|
"suspending with plugged memory is not supported\n");
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtio_reset_device(vdev);
|
||||||
|
vdev->config->del_vqs(vdev);
|
||||||
|
vm->vq = NULL;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int virtio_mem_restore(struct virtio_device *vdev)
|
static int virtio_mem_restore(struct virtio_device *vdev)
|
||||||
{
|
{
|
||||||
return -EPERM;
|
struct virtio_mem *vm = vdev->priv;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = virtio_mem_init_vq(vm);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
virtio_device_ready(vdev);
|
||||||
|
|
||||||
|
/* Let's check if anything changed. */
|
||||||
|
virtio_mem_config_changed(vdev);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -2979,6 +3034,7 @@ static unsigned int virtio_mem_features[] = {
|
|||||||
VIRTIO_MEM_F_ACPI_PXM,
|
VIRTIO_MEM_F_ACPI_PXM,
|
||||||
#endif
|
#endif
|
||||||
VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE,
|
VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE,
|
||||||
|
VIRTIO_MEM_F_PERSISTENT_SUSPEND,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct virtio_device_id virtio_mem_id_table[] = {
|
static const struct virtio_device_id virtio_mem_id_table[] = {
|
||||||
|
@ -90,6 +90,8 @@
|
|||||||
#define VIRTIO_MEM_F_ACPI_PXM 0
|
#define VIRTIO_MEM_F_ACPI_PXM 0
|
||||||
/* unplugged memory must not be accessed */
|
/* unplugged memory must not be accessed */
|
||||||
#define VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE 1
|
#define VIRTIO_MEM_F_UNPLUGGED_INACCESSIBLE 1
|
||||||
|
/* plugged memory will remain plugged when suspending+resuming */
|
||||||
|
#define VIRTIO_MEM_F_PERSISTENT_SUSPEND 2
|
||||||
|
|
||||||
|
|
||||||
/* --- virtio-mem: guest -> host requests --- */
|
/* --- virtio-mem: guest -> host requests --- */
|
||||||
|
Loading…
Reference in New Issue
Block a user