qemu/hw/scsi/vhost-scsi.c
Steve Sistare 89415796f6 cpr: relax vhost migration blockers
vhost blocks migration if logging is not supported to track dirty
memory, and vhost-user blocks it if the log cannot be saved to a shm fd.

vhost-vdpa blocks migration if both hosts do not support all the device's
features using a shadow VQ, for tracking requests and dirty memory.

vhost-scsi blocks migration if storage cannot be shared across hosts,
or if state cannot be migrated.

None of these conditions apply if the old and new qemu processes do
not run concurrently, and if new qemu starts on the same host as old,
which is the case for cpr.

Narrow the scope of these blockers so they only apply to normal mode.
They will not block cpr modes when they are added in subsequent patches.

No functional change until a new mode is added.

Signed-off-by: Steve Sistare <steven.sistare@oracle.com>
Reviewed-by: Juan Quintela <quintela@redhat.com>
Signed-off-by: Juan Quintela <quintela@redhat.com>
Message-ID: <1698263069-406971-5-git-send-email-steven.sistare@oracle.com>
2023-11-01 16:13:59 +01:00

349 lines
10 KiB
C

/*
* vhost_scsi host device
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
*
* Changes for QEMU mainline + tcm_vhost kernel upstream:
* Nicholas Bellinger <nab@risingtidesystems.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include <linux/vhost.h>
#include <sys/ioctl.h>
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "monitor/monitor.h"
#include "migration/blocker.h"
#include "hw/virtio/vhost-scsi.h"
#include "hw/virtio/vhost.h"
#include "hw/virtio/virtio-scsi.h"
#include "hw/virtio/virtio-bus.h"
#include "hw/fw-path-provider.h"
#include "hw/qdev-properties.h"
#include "qemu/cutils.h"
#include "sysemu/sysemu.h"
/* Features supported by host kernel. */
static const int kernel_feature_bits[] = {
VIRTIO_F_NOTIFY_ON_EMPTY,
VIRTIO_RING_F_INDIRECT_DESC,
VIRTIO_RING_F_EVENT_IDX,
VIRTIO_SCSI_F_HOTPLUG,
VIRTIO_F_RING_RESET,
VHOST_INVALID_FEATURE_BIT
};
static int vhost_scsi_set_endpoint(VHostSCSI *s)
{
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
const VhostOps *vhost_ops = vsc->dev.vhost_ops;
struct vhost_scsi_target backend;
int ret;
memset(&backend, 0, sizeof(backend));
pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn);
ret = vhost_ops->vhost_scsi_set_endpoint(&vsc->dev, &backend);
if (ret < 0) {
return -errno;
}
return 0;
}
static void vhost_scsi_clear_endpoint(VHostSCSI *s)
{
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
struct vhost_scsi_target backend;
const VhostOps *vhost_ops = vsc->dev.vhost_ops;
memset(&backend, 0, sizeof(backend));
pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn);
vhost_ops->vhost_scsi_clear_endpoint(&vsc->dev, &backend);
}
static int vhost_scsi_start(VHostSCSI *s)
{
int ret, abi_version;
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
const VhostOps *vhost_ops = vsc->dev.vhost_ops;
Error *local_err = NULL;
ret = vhost_ops->vhost_scsi_get_abi_version(&vsc->dev, &abi_version);
if (ret < 0) {
return -errno;
}
if (abi_version > VHOST_SCSI_ABI_VERSION) {
error_report("vhost-scsi: The running tcm_vhost kernel abi_version:"
" %d is greater than vhost_scsi userspace supports: %d,"
" please upgrade your version of QEMU", abi_version,
VHOST_SCSI_ABI_VERSION);
return -ENOSYS;
}
ret = vhost_scsi_common_start(vsc, &local_err);
if (ret < 0) {
error_reportf_err(local_err, "Error starting vhost-scsi");
return ret;
}
ret = vhost_scsi_set_endpoint(s);
if (ret < 0) {
error_reportf_err(local_err, "Error setting vhost-scsi endpoint");
vhost_scsi_common_stop(vsc);
}
return ret;
}
static void vhost_scsi_stop(VHostSCSI *s)
{
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
vhost_scsi_clear_endpoint(s);
vhost_scsi_common_stop(vsc);
}
static void vhost_scsi_set_status(VirtIODevice *vdev, uint8_t val)
{
VHostSCSI *s = VHOST_SCSI(vdev);
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
bool start = (val & VIRTIO_CONFIG_S_DRIVER_OK);
if (!vdev->vm_running) {
start = false;
}
if (vhost_dev_is_started(&vsc->dev) == start) {
return;
}
if (start) {
int ret;
ret = vhost_scsi_start(s);
if (ret < 0) {
error_report("unable to start vhost-scsi: %s", strerror(-ret));
exit(1);
}
} else {
vhost_scsi_stop(s);
}
}
static void vhost_dummy_handle_output(VirtIODevice *vdev, VirtQueue *vq)
{
}
static int vhost_scsi_pre_save(void *opaque)
{
VHostSCSICommon *vsc = opaque;
/* At this point, backend must be stopped, otherwise
* it might keep writing to memory. */
assert(!vhost_dev_is_started(&vsc->dev));
return 0;
}
static const VMStateDescription vmstate_virtio_vhost_scsi = {
.name = "virtio-vhost_scsi",
.minimum_version_id = 1,
.version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_VIRTIO_DEVICE,
VMSTATE_END_OF_LIST()
},
.pre_save = vhost_scsi_pre_save,
};
static void vhost_scsi_realize(DeviceState *dev, Error **errp)
{
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(dev);
Error *err = NULL;
int vhostfd = -1;
int ret;
struct vhost_virtqueue *vqs = NULL;
if (!vs->conf.wwpn) {
error_setg(errp, "vhost-scsi: missing wwpn");
return;
}
if (vs->conf.vhostfd) {
vhostfd = monitor_fd_param(monitor_cur(), vs->conf.vhostfd, errp);
if (vhostfd == -1) {
error_prepend(errp, "vhost-scsi: unable to parse vhostfd: ");
return;
}
} else {
vhostfd = open("/dev/vhost-scsi", O_RDWR);
if (vhostfd < 0) {
error_setg(errp, "vhost-scsi: open vhost char device failed: %s",
strerror(errno));
return;
}
}
virtio_scsi_common_realize(dev,
vhost_dummy_handle_output,
vhost_dummy_handle_output,
vhost_dummy_handle_output,
&err);
if (err != NULL) {
error_propagate(errp, err);
goto close_fd;
}
if (!vsc->migratable) {
error_setg(&vsc->migration_blocker,
"vhost-scsi does not support migration in all cases. "
"When external environment supports it (Orchestrator migrates "
"target SCSI device state or use shared storage over network), "
"set 'migratable' property to true to enable migration.");
if (migrate_add_blocker_normal(&vsc->migration_blocker, errp) < 0) {
goto free_virtio;
}
}
vsc->dev.nvqs = VHOST_SCSI_VQ_NUM_FIXED + vs->conf.num_queues;
vqs = g_new0(struct vhost_virtqueue, vsc->dev.nvqs);
vsc->dev.vqs = vqs;
vsc->dev.vq_index = 0;
vsc->dev.backend_features = 0;
ret = vhost_dev_init(&vsc->dev, (void *)(uintptr_t)vhostfd,
VHOST_BACKEND_TYPE_KERNEL, 0, errp);
if (ret < 0) {
/*
* vhost_dev_init calls vhost_dev_cleanup on error, which closes
* vhostfd, don't double close it.
*/
vhostfd = -1;
goto free_vqs;
}
/* At present, channel and lun both are 0 for bootable vhost-scsi disk */
vsc->channel = 0;
vsc->lun = 0;
/* Note: we can also get the minimum tpgt from kernel */
vsc->target = vs->conf.boot_tpgt;
return;
free_vqs:
g_free(vqs);
if (!vsc->migratable) {
migrate_del_blocker(&vsc->migration_blocker);
}
free_virtio:
virtio_scsi_common_unrealize(dev);
close_fd:
if (vhostfd >= 0) {
close(vhostfd);
}
return;
}
static void vhost_scsi_unrealize(DeviceState *dev)
{
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(dev);
struct vhost_virtqueue *vqs = vsc->dev.vqs;
if (!vsc->migratable) {
migrate_del_blocker(&vsc->migration_blocker);
}
/* This will stop vhost backend. */
vhost_scsi_set_status(vdev, 0);
vhost_dev_cleanup(&vsc->dev);
g_free(vqs);
virtio_scsi_common_unrealize(dev);
}
static struct vhost_dev *vhost_scsi_get_vhost(VirtIODevice *vdev)
{
VHostSCSI *s = VHOST_SCSI(vdev);
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
return &vsc->dev;
}
static Property vhost_scsi_properties[] = {
DEFINE_PROP_STRING("vhostfd", VirtIOSCSICommon, conf.vhostfd),
DEFINE_PROP_STRING("wwpn", VirtIOSCSICommon, conf.wwpn),
DEFINE_PROP_UINT32("boot_tpgt", VirtIOSCSICommon, conf.boot_tpgt, 0),
DEFINE_PROP_UINT32("num_queues", VirtIOSCSICommon, conf.num_queues,
VIRTIO_SCSI_AUTO_NUM_QUEUES),
DEFINE_PROP_UINT32("virtqueue_size", VirtIOSCSICommon, conf.virtqueue_size,
128),
DEFINE_PROP_BOOL("seg_max_adjust", VirtIOSCSICommon, conf.seg_max_adjust,
true),
DEFINE_PROP_UINT32("max_sectors", VirtIOSCSICommon, conf.max_sectors,
0xFFFF),
DEFINE_PROP_UINT32("cmd_per_lun", VirtIOSCSICommon, conf.cmd_per_lun, 128),
DEFINE_PROP_BIT64("t10_pi", VHostSCSICommon, host_features,
VIRTIO_SCSI_F_T10_PI,
false),
DEFINE_PROP_BOOL("migratable", VHostSCSICommon, migratable, false),
DEFINE_PROP_END_OF_LIST(),
};
static void vhost_scsi_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
FWPathProviderClass *fwc = FW_PATH_PROVIDER_CLASS(klass);
device_class_set_props(dc, vhost_scsi_properties);
dc->vmsd = &vmstate_virtio_vhost_scsi;
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
vdc->realize = vhost_scsi_realize;
vdc->unrealize = vhost_scsi_unrealize;
vdc->get_features = vhost_scsi_common_get_features;
vdc->set_config = vhost_scsi_common_set_config;
vdc->set_status = vhost_scsi_set_status;
vdc->get_vhost = vhost_scsi_get_vhost;
fwc->get_dev_path = vhost_scsi_common_get_fw_dev_path;
}
static void vhost_scsi_instance_init(Object *obj)
{
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(obj);
vsc->feature_bits = kernel_feature_bits;
device_add_bootindex_property(obj, &vsc->bootindex, "bootindex", NULL,
DEVICE(vsc));
}
static const TypeInfo vhost_scsi_info = {
.name = TYPE_VHOST_SCSI,
.parent = TYPE_VHOST_SCSI_COMMON,
.instance_size = sizeof(VHostSCSI),
.class_init = vhost_scsi_class_init,
.instance_init = vhost_scsi_instance_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_FW_PATH_PROVIDER },
{ }
},
};
static void virtio_register_types(void)
{
type_register_static(&vhost_scsi_info);
}
type_init(virtio_register_types)