qemu/hw/scsi/vhost-scsi.c
Jonah Palmer b937fa8963 vhost/vhost-user: Add VIRTIO_F_NOTIFICATION_DATA to vhost feature bits
Add support for the VIRTIO_F_NOTIFICATION_DATA feature across a variety
of vhost devices.

The inclusion of VIRTIO_F_NOTIFICATION_DATA in the feature bits arrays
for these devices ensures that the backend is capable of offering and
providing support for this feature, and that it can be disabled if the
backend does not support it.

Tested-by: Lei Yang <leiyang@redhat.com>
Reviewed-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Jonah Palmer <jonah.palmer@oracle.com>
Message-Id: <20240315165557.26942-6-jonah.palmer@oracle.com>
Acked-by: Srujana Challa <schalla@marvell.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2024-07-01 14:56:23 -04:00

413 lines
12 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,
VIRTIO_F_NOTIFICATION_DATA,
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_report("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 = (const VMStateField[]) {
VMSTATE_VIRTIO_DEVICE,
VMSTATE_END_OF_LIST()
},
.pre_save = vhost_scsi_pre_save,
};
static int vhost_scsi_set_workers(VHostSCSICommon *vsc, bool per_virtqueue)
{
struct vhost_dev *dev = &vsc->dev;
struct vhost_vring_worker vq_worker;
struct vhost_worker_state worker;
int i, ret;
/* Use default worker */
if (!per_virtqueue || dev->nvqs == VHOST_SCSI_VQ_NUM_FIXED + 1) {
return 0;
}
/*
* ctl/evt share the first worker since it will be rare for them
* to send cmds while IO is running.
*/
for (i = VHOST_SCSI_VQ_NUM_FIXED + 1; i < dev->nvqs; i++) {
memset(&worker, 0, sizeof(worker));
ret = dev->vhost_ops->vhost_new_worker(dev, &worker);
if (ret == -ENOTTY) {
/*
* worker ioctls are not implemented so just ignore and
* and continue device setup.
*/
warn_report("vhost-scsi: Backend supports a single worker. "
"Ignoring worker_per_virtqueue=true setting.");
ret = 0;
break;
} else if (ret) {
break;
}
memset(&vq_worker, 0, sizeof(vq_worker));
vq_worker.worker_id = worker.worker_id;
vq_worker.index = i;
ret = dev->vhost_ops->vhost_attach_vring_worker(dev, &vq_worker);
if (ret == -ENOTTY) {
/*
* It's a bug for the kernel to have supported the worker creation
* ioctl but not attach.
*/
dev->vhost_ops->vhost_free_worker(dev, &worker);
break;
} else if (ret) {
break;
}
}
return ret;
}
static void vhost_scsi_realize(DeviceState *dev, Error **errp)
{
ERRP_GUARD();
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;
}
ret = vhost_scsi_set_workers(vsc, vs->conf.worker_per_virtqueue);
if (ret < 0) {
error_setg(errp, "vhost-scsi: vhost worker setup failed: %s",
strerror(-ret));
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_BOOL("worker_per_virtqueue", VirtIOSCSICommon,
conf.worker_per_virtqueue, 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)