|  | /* | 
|  | * 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) |