349 lines
12 KiB
Diff
349 lines
12 KiB
Diff
From 91e9a0fbb3d6ee8726ea0ef17ff17404f823577a Mon Sep 17 00:00:00 2001
|
|
From: Stefan Hajnoczi <stefanha@redhat.com>
|
|
Date: Thu, 29 Jan 2026 16:20:34 -0500
|
|
Subject: [PATCH 5/7] scsi: save/load SCSI reservation state
|
|
|
|
RH-Author: Stefan Hajnoczi <stefanha@redhat.com>
|
|
RH-MergeRequest: 464: scsi: persistent reservation live migration
|
|
RH-Jira: RHEL-132749
|
|
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
RH-Acked-by: Kevin Wolf <kwolf@redhat.com>
|
|
RH-Commit: [4/5] 2ebcfedf1a9e54433ed1ba3593d4fbcbadcd644b (stefanha/centos-stream-qemu-kvm)
|
|
|
|
Add a vmstate subsection to SCSIDiskState so that scsi-block devices can
|
|
transfer their reservation state during live migration. Upon loading the
|
|
subsection, the destination QEMU invokes the PERSISTENT RESERVE OUT
|
|
command's PREEMPT service action to atomically move the reservation from
|
|
the source I_T nexus to the destination I_T nexus. This results in
|
|
transparent live migration of SCSI reservations.
|
|
|
|
This approach is incomplete since SCSI reservations are cooperative and
|
|
other hosts could interfere. Neither the source QEMU nor the destination
|
|
QEMU are aware of changes made by other hosts. The assumption is that
|
|
reservation is not taken over by a third host without cooperation from
|
|
the source host.
|
|
|
|
I considered adding the vmstate subsection to SCSIDevice instead of
|
|
SCSIDiskState, since reservations are part of the SCSI Primary Commands
|
|
that other devices apart from disks could support. However, due to
|
|
fragility of migrating reservations, we will probably limit support to
|
|
scsi-block and maybe scsi-disk in the future. In the end, I think it
|
|
makes sense to place this within scsi-disk.c.
|
|
|
|
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
|
|
Message-id: 20260129212035.219676-5-stefanha@redhat.com
|
|
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
(cherry picked from commit ab57b51f1375b6a6f098a74c6f79207a9630948d)
|
|
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
|
|
Conflicts:
|
|
- hw/core/machine.c
|
|
|
|
Downstream does not have hw_compat_10_2. Make sure that machine types
|
|
prior to RHEL 10.2 default to migrate-pr=off.
|
|
|
|
- hw/scsi/scsi-disk.c
|
|
|
|
Downstream is missing commit 40de712a89d8f ("migration: Add
|
|
error-parameterized function variants in VMSD struct"), so add a
|
|
.post_load() wrapper function that reports the Error and returns
|
|
-EINVAL.
|
|
---
|
|
hw/core/machine.c | 1 +
|
|
hw/scsi/scsi-disk.c | 99 +++++++++++++++++++++++++++++++++++++++++-
|
|
hw/scsi/scsi-generic.c | 83 +++++++++++++++++++++++++++++++++++
|
|
hw/scsi/trace-events | 1 +
|
|
include/hw/scsi/scsi.h | 1 +
|
|
5 files changed, 184 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/hw/core/machine.c b/hw/core/machine.c
|
|
index 2a1a42cebc..2b339f6a13 100644
|
|
--- a/hw/core/machine.c
|
|
+++ b/hw/core/machine.c
|
|
@@ -295,6 +295,7 @@ const char *rhel_old_machine_deprecation =
|
|
"machine types for previous major releases are deprecated";
|
|
|
|
GlobalProperty hw_compat_rhel_10_2[] = {
|
|
+ { "scsi-block", "migrate-pr", "off" },
|
|
/* hw_compat_rhel_10_2 from hw_compat_10_0 */
|
|
{ "scsi-hd", "dpofua", "off" },
|
|
/* hw_compat_rhel_10_2 from hw_compat_10_0 */
|
|
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
|
|
index c736988ef6..e7b821ad19 100644
|
|
--- a/hw/scsi/scsi-disk.c
|
|
+++ b/hw/scsi/scsi-disk.c
|
|
@@ -28,6 +28,7 @@
|
|
#include "qemu/hw-version.h"
|
|
#include "qemu/memalign.h"
|
|
#include "hw/scsi/scsi.h"
|
|
+#include "migration/misc.h"
|
|
#include "migration/qemu-file-types.h"
|
|
#include "migration/vmstate.h"
|
|
#include "hw/scsi/emulation.h"
|
|
@@ -122,6 +123,7 @@ struct SCSIDiskState {
|
|
*/
|
|
uint16_t rotation_rate;
|
|
bool migrate_emulated_scsi_request;
|
|
+ NotifierWithReturn migration_notifier;
|
|
};
|
|
|
|
static void scsi_free_request(SCSIRequest *req)
|
|
@@ -2737,6 +2739,29 @@ static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun,
|
|
}
|
|
|
|
#ifdef __linux__
|
|
+/*
|
|
+ * Preempt on the SCSI Persistent Reservation on the source when migration
|
|
+ * fails because the destination may have already preempted and we need to get
|
|
+ * the reservation back.
|
|
+ */
|
|
+static int scsi_block_migration_notifier(NotifierWithReturn *notifier,
|
|
+ MigrationEvent *e, Error **errp)
|
|
+{
|
|
+ if (e->type == MIG_EVENT_PRECOPY_FAILED) {
|
|
+ SCSIDiskState *s =
|
|
+ container_of(notifier, SCSIDiskState, migration_notifier);
|
|
+ SCSIDevice *d = &s->qdev;
|
|
+ Error *local_err = NULL;
|
|
+
|
|
+ if (!scsi_generic_pr_state_preempt(d, &local_err)) {
|
|
+ /* MIG_EVENT_PRECOPY_FAILED cannot fail, so just warn */
|
|
+ error_prepend(&local_err, "scsi-block migration rollback: ");
|
|
+ warn_report_err(local_err);
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int get_device_type(SCSIDiskState *s)
|
|
{
|
|
uint8_t cmd[16];
|
|
@@ -2815,6 +2840,16 @@ static void scsi_block_realize(SCSIDevice *dev, Error **errp)
|
|
|
|
scsi_realize(&s->qdev, errp);
|
|
scsi_generic_read_device_inquiry(&s->qdev);
|
|
+
|
|
+ migration_add_notifier(&s->migration_notifier,
|
|
+ scsi_block_migration_notifier);
|
|
+}
|
|
+
|
|
+static void scsi_block_unrealize(SCSIDevice *dev)
|
|
+{
|
|
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
|
|
+
|
|
+ migration_remove_notifier(&s->migration_notifier);
|
|
}
|
|
|
|
typedef struct SCSIBlockReq {
|
|
@@ -3209,6 +3244,60 @@ static const Property scsi_hd_properties[] = {
|
|
DEFINE_BLOCK_CHS_PROPERTIES(SCSIDiskState, qdev.conf),
|
|
};
|
|
|
|
+#ifdef __linux__
|
|
+static bool scsi_disk_pr_state_post_load_errp(void *opaque, int version_id,
|
|
+ Error **errp)
|
|
+{
|
|
+ SCSIDiskState *s = opaque;
|
|
+ SCSIDevice *dev = &s->qdev;
|
|
+
|
|
+ return scsi_generic_pr_state_preempt(dev, errp);
|
|
+}
|
|
+
|
|
+static int scsi_disk_pr_state_post_load(void *opaque, int version_id)
|
|
+{
|
|
+ SCSIDiskState *s = opaque;
|
|
+ Error *errp = NULL;
|
|
+
|
|
+ if (scsi_disk_pr_state_post_load_errp(s, version_id, &errp)) {
|
|
+ return 0;
|
|
+ } else {
|
|
+ error_report_err(errp);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool scsi_disk_pr_state_needed(void *opaque)
|
|
+{
|
|
+ SCSIDiskState *s = opaque;
|
|
+ SCSIPRState *pr_state = &s->qdev.pr_state;
|
|
+ bool ret;
|
|
+
|
|
+ if (!s->qdev.migrate_pr) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /* A reservation requires a key, so checking this field is enough */
|
|
+ WITH_QEMU_LOCK_GUARD(&pr_state->mutex) {
|
|
+ ret = pr_state->key;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const VMStateDescription vmstate_scsi_disk_pr_state = {
|
|
+ .name = "scsi-disk/pr",
|
|
+ .version_id = 1,
|
|
+ .minimum_version_id = 1,
|
|
+ .post_load = scsi_disk_pr_state_post_load,
|
|
+ .needed = scsi_disk_pr_state_needed,
|
|
+ .fields = (const VMStateField[]) {
|
|
+ VMSTATE_UINT64(qdev.pr_state.key, SCSIDiskState),
|
|
+ VMSTATE_UINT8(qdev.pr_state.resv_type, SCSIDiskState),
|
|
+ VMSTATE_END_OF_LIST()
|
|
+ }
|
|
+};
|
|
+#endif /* __linux__ */
|
|
+
|
|
static const VMStateDescription vmstate_scsi_disk_state = {
|
|
.name = "scsi-disk",
|
|
.version_id = 1,
|
|
@@ -3221,7 +3310,13 @@ static const VMStateDescription vmstate_scsi_disk_state = {
|
|
VMSTATE_BOOL(tray_open, SCSIDiskState),
|
|
VMSTATE_BOOL(tray_locked, SCSIDiskState),
|
|
VMSTATE_END_OF_LIST()
|
|
- }
|
|
+ },
|
|
+ .subsections = (const VMStateDescription * const []) {
|
|
+#ifdef __linux__
|
|
+ &vmstate_scsi_disk_pr_state,
|
|
+#endif
|
|
+ NULL
|
|
+ },
|
|
};
|
|
|
|
static void scsi_hd_class_initfn(ObjectClass *klass, const void *data)
|
|
@@ -3301,6 +3396,7 @@ static const Property scsi_block_properties[] = {
|
|
-1),
|
|
DEFINE_PROP_UINT32("io_timeout", SCSIDiskState, qdev.io_timeout,
|
|
DEFAULT_IO_TIMEOUT),
|
|
+ DEFINE_PROP_BOOL("migrate-pr", SCSIDiskState, qdev.migrate_pr, true),
|
|
};
|
|
|
|
static void scsi_block_class_initfn(ObjectClass *klass, const void *data)
|
|
@@ -3310,6 +3406,7 @@ static void scsi_block_class_initfn(ObjectClass *klass, const void *data)
|
|
SCSIDiskClass *sdc = SCSI_DISK_BASE_CLASS(klass);
|
|
|
|
sc->realize = scsi_block_realize;
|
|
+ sc->unrealize = scsi_block_unrealize;
|
|
sc->alloc_req = scsi_block_new_request;
|
|
sc->parse_cdb = scsi_block_parse_cdb;
|
|
sdc->dma_readv = scsi_block_dma_readv;
|
|
diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
|
|
index a6280eaa87..b8b3f399f0 100644
|
|
--- a/hw/scsi/scsi-generic.c
|
|
+++ b/hw/scsi/scsi-generic.c
|
|
@@ -423,6 +423,89 @@ static void scsi_handle_persistent_reserve_out_reply(
|
|
}
|
|
}
|
|
|
|
+static bool scsi_generic_pr_register(SCSIDevice *s, uint64_t key, Error **errp)
|
|
+{
|
|
+ uint8_t cmd[10] = {};
|
|
+ uint8_t buf[24] = {};
|
|
+ uint64_t key_be = cpu_to_be64(key);
|
|
+ int ret;
|
|
+
|
|
+ cmd[0] = PERSISTENT_RESERVE_OUT;
|
|
+ cmd[1] = PRO_REGISTER;
|
|
+ cmd[8] = sizeof(buf);
|
|
+ memcpy(&buf[8], &key_be, sizeof(key_be));
|
|
+
|
|
+ ret = scsi_SG_IO(s->conf.blk, SG_DXFER_TO_DEV, cmd, sizeof(cmd),
|
|
+ buf, sizeof(buf), s->io_timeout, errp);
|
|
+ if (ret < 0) {
|
|
+ error_prepend(errp, "PERSISTENT RESERVE OUT with REGISTER");
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool scsi_generic_pr_preempt(SCSIDevice *s, uint64_t key,
|
|
+ uint8_t resv_type, Error **errp)
|
|
+{
|
|
+ uint8_t cmd[10] = {};
|
|
+ uint8_t buf[24] = {};
|
|
+ uint64_t key_be = cpu_to_be64(key);
|
|
+ int ret;
|
|
+
|
|
+ cmd[0] = PERSISTENT_RESERVE_OUT;
|
|
+ cmd[1] = PRO_PREEMPT;
|
|
+ cmd[2] = resv_type & 0xf;
|
|
+ cmd[8] = sizeof(buf);
|
|
+ memcpy(&buf[0], &key_be, sizeof(key_be));
|
|
+ memcpy(&buf[8], &key_be, sizeof(key_be));
|
|
+
|
|
+ ret = scsi_SG_IO(s->conf.blk, SG_DXFER_TO_DEV, cmd, sizeof(cmd),
|
|
+ buf, sizeof(buf), s->io_timeout, errp);
|
|
+ if (ret < 0) {
|
|
+ error_prepend(errp, "PERSISTENT RESERVE OUT with PREEMPT");
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
+/* Register keys and preempt reservations after live migration */
|
|
+bool scsi_generic_pr_state_preempt(SCSIDevice *s, Error **errp)
|
|
+{
|
|
+ SCSIPRState *pr_state = &s->pr_state;
|
|
+ uint64_t key;
|
|
+ uint8_t resv_type;
|
|
+
|
|
+ WITH_QEMU_LOCK_GUARD(&pr_state->mutex) {
|
|
+ key = pr_state->key;
|
|
+ resv_type = pr_state->resv_type;
|
|
+ }
|
|
+
|
|
+ trace_scsi_generic_pr_state_preempt(key, resv_type);
|
|
+
|
|
+ if (key) {
|
|
+ if (!scsi_generic_pr_register(s, key, errp)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Two cases:
|
|
+ *
|
|
+ * 1. There is no reservation (resv_type is 0) and the other I_T nexus
|
|
+ * will be unregistered. This is important so the source host does
|
|
+ * not leak registered keys across live migration.
|
|
+ *
|
|
+ * 2. There is a reservation (resv_type is not 0) and the other I_T
|
|
+ * nexus will be unregistered and its reservation is atomically
|
|
+ * taken over by us. This is the scenario where a reservation is
|
|
+ * migrated along with the guest.
|
|
+ */
|
|
+ if (!scsi_generic_pr_preempt(s, key, resv_type, errp)) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
static void scsi_read_complete(void * opaque, int ret)
|
|
{
|
|
SCSIGenericReq *r = (SCSIGenericReq *)opaque;
|
|
diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events
|
|
index fdb87a237f..ab7a6f4cea 100644
|
|
--- a/hw/scsi/trace-events
|
|
+++ b/hw/scsi/trace-events
|
|
@@ -362,3 +362,4 @@ scsi_generic_aio_sgio_command(uint32_t tag, uint8_t cmd, uint32_t timeout) "gene
|
|
scsi_generic_ioctl_sgio_command(uint8_t cmd, uint32_t timeout) "generic ioctl sgio: cmd=0x%x timeout=%u"
|
|
scsi_generic_ioctl_sgio_done(uint8_t cmd, int ret, uint8_t status, uint8_t host_status) "generic ioctl sgio: cmd=0x%x ret=%d status=0x%x host_status=0x%x"
|
|
scsi_generic_persistent_reserve_out_reply(uint8_t service_action, uint8_t resv_type, uint64_t old_key, uint64_t new_key) "persistent reserve out reply service_action=%u resv_type=%u old_key=0x%" PRIx64 " new_key=0x%" PRIx64
|
|
+scsi_generic_pr_state_preempt(uint64_t key, uint8_t resv_type) "key=0x%" PRIx64 " resv_type=%u"
|
|
diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h
|
|
index 7e120fdd6a..f61c63c5ea 100644
|
|
--- a/include/hw/scsi/scsi.h
|
|
+++ b/include/hw/scsi/scsi.h
|
|
@@ -253,6 +253,7 @@ SCSIDevice *scsi_device_get(SCSIBus *bus, int channel, int target, int lun);
|
|
|
|
/* scsi-generic.c. */
|
|
extern const SCSIReqOps scsi_generic_req_ops;
|
|
+bool scsi_generic_pr_state_preempt(SCSIDevice *s, Error **errp);
|
|
|
|
/* scsi-disk.c */
|
|
#define SCSI_DISK_QUIRK_MODE_PAGE_APPLE_VENDOR 0
|
|
--
|
|
2.47.3
|
|
|