#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"
*/
uint16_t rotation_rate;
bool migrate_emulated_scsi_request;
+ NotifierWithReturn migration_notifier;
};
static void scsi_free_request(SCSIRequest *req)
}
#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];
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 {
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 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_errp = scsi_disk_pr_state_post_load_errp,
+ .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,
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)
-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)
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;
}
}
+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;