c03213fdc9
Add support for the VIRTIO_F_IN_ORDER feature across a variety of vhost devices. The inclusion of VIRTIO_F_IN_ORDER 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. Acked-by: Eugenio Pérez <eperezma@redhat.com> Signed-off-by: Jonah Palmer <jonah.palmer@oracle.com> Message-Id: <20240710125522.4168043-6-jonah.palmer@oracle.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
438 lines
12 KiB
C
438 lines
12 KiB
C
/*
|
|
* vhost-user-scsi host device
|
|
*
|
|
* Copyright (c) 2016 Nutanix Inc. All rights reserved.
|
|
*
|
|
* Author:
|
|
* Felipe Franciosi <felipe@nutanix.com>
|
|
*
|
|
* This work is largely based on the "vhost-scsi" implementation by:
|
|
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
|
|
* 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 "qapi/error.h"
|
|
#include "qemu/error-report.h"
|
|
#include "hw/fw-path-provider.h"
|
|
#include "hw/qdev-core.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/qdev-properties-system.h"
|
|
#include "hw/virtio/vhost.h"
|
|
#include "hw/virtio/vhost-backend.h"
|
|
#include "hw/virtio/vhost-user-scsi.h"
|
|
#include "hw/virtio/virtio.h"
|
|
#include "chardev/char-fe.h"
|
|
#include "sysemu/sysemu.h"
|
|
|
|
/* Features supported by the host application */
|
|
static const int user_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_IN_ORDER,
|
|
VIRTIO_F_NOTIFICATION_DATA,
|
|
VHOST_INVALID_FEATURE_BIT
|
|
};
|
|
|
|
static int vhost_user_scsi_start(VHostUserSCSI *s, Error **errp)
|
|
{
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
int ret;
|
|
|
|
ret = vhost_scsi_common_start(vsc, errp);
|
|
s->started_vu = !(ret < 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void vhost_user_scsi_stop(VHostUserSCSI *s)
|
|
{
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
|
|
if (!s->started_vu) {
|
|
return;
|
|
}
|
|
s->started_vu = false;
|
|
|
|
vhost_scsi_common_stop(vsc);
|
|
}
|
|
|
|
static void vhost_user_scsi_set_status(VirtIODevice *vdev, uint8_t status)
|
|
{
|
|
VHostUserSCSI *s = (VHostUserSCSI *)vdev;
|
|
DeviceState *dev = DEVICE(vdev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
bool should_start = virtio_device_should_start(vdev, status);
|
|
Error *local_err = NULL;
|
|
int ret;
|
|
|
|
if (!s->connected) {
|
|
return;
|
|
}
|
|
|
|
if (vhost_dev_is_started(&vsc->dev) == should_start) {
|
|
return;
|
|
}
|
|
|
|
if (should_start) {
|
|
ret = vhost_user_scsi_start(s, &local_err);
|
|
if (ret < 0) {
|
|
error_reportf_err(local_err,
|
|
"unable to start vhost-user-scsi: %s: ",
|
|
strerror(-ret));
|
|
qemu_chr_fe_disconnect(&vs->conf.chardev);
|
|
}
|
|
} else {
|
|
vhost_user_scsi_stop(s);
|
|
}
|
|
}
|
|
|
|
static void vhost_user_scsi_handle_output(VirtIODevice *vdev, VirtQueue *vq)
|
|
{
|
|
VHostUserSCSI *s = (VHostUserSCSI *)vdev;
|
|
DeviceState *dev = DEVICE(vdev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
|
|
Error *local_err = NULL;
|
|
int i, ret;
|
|
|
|
if (!vdev->start_on_kick) {
|
|
return;
|
|
}
|
|
|
|
if (!s->connected) {
|
|
return;
|
|
}
|
|
|
|
if (vhost_dev_is_started(&vsc->dev)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Some guests kick before setting VIRTIO_CONFIG_S_DRIVER_OK so start
|
|
* vhost here instead of waiting for .set_status().
|
|
*/
|
|
ret = vhost_user_scsi_start(s, &local_err);
|
|
if (ret < 0) {
|
|
error_reportf_err(local_err, "vhost-user-scsi: vhost start failed: ");
|
|
qemu_chr_fe_disconnect(&vs->conf.chardev);
|
|
return;
|
|
}
|
|
|
|
/* Kick right away to begin processing requests already in vring */
|
|
for (i = 0; i < vsc->dev.nvqs; i++) {
|
|
VirtQueue *kick_vq = virtio_get_queue(vdev, i);
|
|
|
|
if (!virtio_queue_get_desc_addr(vdev, i)) {
|
|
continue;
|
|
}
|
|
event_notifier_set(virtio_queue_get_host_notifier(kick_vq));
|
|
}
|
|
}
|
|
|
|
static int vhost_user_scsi_connect(DeviceState *dev, Error **errp)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
|
VHostUserSCSI *s = VHOST_USER_SCSI(vdev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
int ret = 0;
|
|
|
|
if (s->connected) {
|
|
return 0;
|
|
}
|
|
|
|
vsc->dev.num_queues = vs->conf.num_queues;
|
|
vsc->dev.nvqs = VIRTIO_SCSI_VQ_NUM_FIXED + vs->conf.num_queues;
|
|
vsc->dev.vqs = s->vhost_vqs;
|
|
vsc->dev.vq_index = 0;
|
|
vsc->dev.backend_features = 0;
|
|
|
|
ret = vhost_dev_init(&vsc->dev, &s->vhost_user, VHOST_BACKEND_TYPE_USER, 0,
|
|
errp);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
s->connected = true;
|
|
|
|
/* restore vhost state */
|
|
if (virtio_device_started(vdev, vdev->status)) {
|
|
ret = vhost_user_scsi_start(s, errp);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void vhost_user_scsi_event(void *opaque, QEMUChrEvent event);
|
|
|
|
static void vhost_user_scsi_disconnect(DeviceState *dev)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
|
VHostUserSCSI *s = VHOST_USER_SCSI(vdev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
|
|
if (!s->connected) {
|
|
goto done;
|
|
}
|
|
s->connected = false;
|
|
|
|
vhost_user_scsi_stop(s);
|
|
|
|
vhost_dev_cleanup(&vsc->dev);
|
|
|
|
done:
|
|
/* Re-instate the event handler for new connections */
|
|
qemu_chr_fe_set_handlers(&vs->conf.chardev, NULL, NULL,
|
|
vhost_user_scsi_event, NULL, dev, NULL, true);
|
|
}
|
|
|
|
static void vhost_user_scsi_event(void *opaque, QEMUChrEvent event)
|
|
{
|
|
DeviceState *dev = opaque;
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
|
VHostUserSCSI *s = VHOST_USER_SCSI(vdev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
Error *local_err = NULL;
|
|
|
|
switch (event) {
|
|
case CHR_EVENT_OPENED:
|
|
if (vhost_user_scsi_connect(dev, &local_err) < 0) {
|
|
error_report_err(local_err);
|
|
qemu_chr_fe_disconnect(&vs->conf.chardev);
|
|
return;
|
|
}
|
|
break;
|
|
case CHR_EVENT_CLOSED:
|
|
/* defer close until later to avoid circular close */
|
|
vhost_user_async_close(dev, &vs->conf.chardev, &vsc->dev,
|
|
vhost_user_scsi_disconnect);
|
|
break;
|
|
case CHR_EVENT_BREAK:
|
|
case CHR_EVENT_MUX_IN:
|
|
case CHR_EVENT_MUX_OUT:
|
|
/* Ignore */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int vhost_user_scsi_realize_connect(VHostUserSCSI *s, Error **errp)
|
|
{
|
|
DeviceState *dev = DEVICE(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
int ret;
|
|
|
|
s->connected = false;
|
|
|
|
ret = qemu_chr_fe_wait_connected(&vs->conf.chardev, errp);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = vhost_user_scsi_connect(dev, errp);
|
|
if (ret < 0) {
|
|
qemu_chr_fe_disconnect(&vs->conf.chardev);
|
|
return ret;
|
|
}
|
|
assert(s->connected);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vhost_user_scsi_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
ERRP_GUARD();
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
VHostUserSCSI *s = VHOST_USER_SCSI(dev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
Error *err = NULL;
|
|
int ret;
|
|
int retries = VU_REALIZE_CONN_RETRIES;
|
|
|
|
if (!vs->conf.chardev.chr) {
|
|
error_setg(errp, "vhost-user-scsi: missing chardev");
|
|
return;
|
|
}
|
|
|
|
virtio_scsi_common_realize(dev, vhost_user_scsi_handle_output,
|
|
vhost_user_scsi_handle_output,
|
|
vhost_user_scsi_handle_output, &err);
|
|
if (err != NULL) {
|
|
error_propagate(errp, err);
|
|
return;
|
|
}
|
|
|
|
if (!vhost_user_init(&s->vhost_user, &vs->conf.chardev, errp)) {
|
|
goto free_virtio;
|
|
}
|
|
|
|
vsc->inflight = g_new0(struct vhost_inflight, 1);
|
|
s->vhost_vqs = g_new0(struct vhost_virtqueue,
|
|
VIRTIO_SCSI_VQ_NUM_FIXED + vs->conf.num_queues);
|
|
|
|
assert(!*errp);
|
|
do {
|
|
if (*errp) {
|
|
error_prepend(errp, "Reconnecting after error: ");
|
|
error_report_err(*errp);
|
|
*errp = NULL;
|
|
}
|
|
ret = vhost_user_scsi_realize_connect(s, errp);
|
|
} while (ret < 0 && retries--);
|
|
|
|
if (ret < 0) {
|
|
goto free_vhost;
|
|
}
|
|
|
|
/* we're fully initialized, now we can operate, so add the handler */
|
|
qemu_chr_fe_set_handlers(&vs->conf.chardev, NULL, NULL,
|
|
vhost_user_scsi_event, NULL, (void *)dev,
|
|
NULL, true);
|
|
/* Channel and lun both are 0 for bootable vhost-user-scsi disk */
|
|
vsc->channel = 0;
|
|
vsc->lun = 0;
|
|
vsc->target = vs->conf.boot_tpgt;
|
|
|
|
return;
|
|
|
|
free_vhost:
|
|
g_free(s->vhost_vqs);
|
|
s->vhost_vqs = NULL;
|
|
g_free(vsc->inflight);
|
|
vsc->inflight = NULL;
|
|
vhost_user_cleanup(&s->vhost_user);
|
|
|
|
free_virtio:
|
|
virtio_scsi_common_unrealize(dev);
|
|
}
|
|
|
|
static void vhost_user_scsi_unrealize(DeviceState *dev)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
|
VHostUserSCSI *s = VHOST_USER_SCSI(dev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
|
|
|
|
/* This will stop the vhost backend. */
|
|
vhost_user_scsi_set_status(vdev, 0);
|
|
qemu_chr_fe_set_handlers(&vs->conf.chardev, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, false);
|
|
|
|
vhost_dev_cleanup(&vsc->dev);
|
|
g_free(s->vhost_vqs);
|
|
s->vhost_vqs = NULL;
|
|
|
|
vhost_dev_free_inflight(vsc->inflight);
|
|
g_free(vsc->inflight);
|
|
vsc->inflight = NULL;
|
|
|
|
vhost_user_cleanup(&s->vhost_user);
|
|
virtio_scsi_common_unrealize(dev);
|
|
}
|
|
|
|
static Property vhost_user_scsi_properties[] = {
|
|
DEFINE_PROP_CHR("chardev", VirtIOSCSICommon, conf.chardev),
|
|
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_UINT32("max_sectors", VirtIOSCSICommon, conf.max_sectors,
|
|
0xFFFF),
|
|
DEFINE_PROP_UINT32("cmd_per_lun", VirtIOSCSICommon, conf.cmd_per_lun, 128),
|
|
DEFINE_PROP_BIT64("hotplug", VHostSCSICommon, host_features,
|
|
VIRTIO_SCSI_F_HOTPLUG,
|
|
true),
|
|
DEFINE_PROP_BIT64("param_change", VHostSCSICommon, host_features,
|
|
VIRTIO_SCSI_F_CHANGE,
|
|
true),
|
|
DEFINE_PROP_BIT64("t10_pi", VHostSCSICommon, host_features,
|
|
VIRTIO_SCSI_F_T10_PI,
|
|
false),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void vhost_user_scsi_reset(VirtIODevice *vdev)
|
|
{
|
|
VHostUserSCSI *s = VHOST_USER_SCSI(vdev);
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
|
|
|
|
vhost_dev_free_inflight(vsc->inflight);
|
|
}
|
|
|
|
static struct vhost_dev *vhost_user_scsi_get_vhost(VirtIODevice *vdev)
|
|
{
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(vdev);
|
|
return &vsc->dev;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_vhost_scsi = {
|
|
.name = "virtio-scsi",
|
|
.minimum_version_id = 1,
|
|
.version_id = 1,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_VIRTIO_DEVICE,
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
};
|
|
|
|
static void vhost_user_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_user_scsi_properties);
|
|
dc->vmsd = &vmstate_vhost_scsi;
|
|
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
|
vdc->realize = vhost_user_scsi_realize;
|
|
vdc->unrealize = vhost_user_scsi_unrealize;
|
|
vdc->get_features = vhost_scsi_common_get_features;
|
|
vdc->set_config = vhost_scsi_common_set_config;
|
|
vdc->set_status = vhost_user_scsi_set_status;
|
|
fwc->get_dev_path = vhost_scsi_common_get_fw_dev_path;
|
|
vdc->reset = vhost_user_scsi_reset;
|
|
vdc->get_vhost = vhost_user_scsi_get_vhost;
|
|
}
|
|
|
|
static void vhost_user_scsi_instance_init(Object *obj)
|
|
{
|
|
VHostSCSICommon *vsc = VHOST_SCSI_COMMON(obj);
|
|
|
|
vsc->feature_bits = user_feature_bits;
|
|
|
|
/* Add the bootindex property for this object */
|
|
device_add_bootindex_property(obj, &vsc->bootindex, "bootindex", NULL,
|
|
DEVICE(vsc));
|
|
}
|
|
|
|
static const TypeInfo vhost_user_scsi_info = {
|
|
.name = TYPE_VHOST_USER_SCSI,
|
|
.parent = TYPE_VHOST_SCSI_COMMON,
|
|
.instance_size = sizeof(VHostUserSCSI),
|
|
.class_init = vhost_user_scsi_class_init,
|
|
.instance_init = vhost_user_scsi_instance_init,
|
|
.interfaces = (InterfaceInfo[]) {
|
|
{ TYPE_FW_PATH_PROVIDER },
|
|
{ }
|
|
},
|
|
};
|
|
|
|
static void virtio_register_types(void)
|
|
{
|
|
type_register_static(&vhost_user_scsi_info);
|
|
}
|
|
|
|
type_init(virtio_register_types)
|